Skip to content

Commit

Permalink
Fixed render issue with asChild
Browse files Browse the repository at this point in the history
  • Loading branch information
Saartje87 committed Sep 1, 2024
1 parent 01c3f7b commit 05eb6e3
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/itchy-icons-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@blockle/blocks': patch
---

Fixed render issue with asChild
3 changes: 1 addition & 2 deletions src/components/layout/Box/Box.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { expect } from '@storybook/test';
import { Meta, StoryObj } from '@storybook/react';
import { within } from '@storybook/test';
import { expect, within } from '@storybook/test';
import {
responsiveProperties,
unresponsiveProperties,
Expand Down
54 changes: 54 additions & 0 deletions src/lib/asChildRenderer/createAsChildTemplate.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ const TestComponent: React.FC<TestComponentProps> = ({ children, asChild, classN
);
};

const AdvancedTestComponent: React.FC<TestComponentProps> = ({ children, asChild, className }) => {
return (
<Template asChild={asChild} className={className}>
<span>A</span>
<Slot>{children}</Slot>
<span>B</span>
</Template>
);
};

describe('asChildRenderer', () => {
it('should render the default element', () => {
const { getByText, container } = render(<TestComponent>Default</TestComponent>);
Expand Down Expand Up @@ -69,4 +79,48 @@ describe('asChildRenderer', () => {
expect(targetElement).toHaveClass('test');
expect(targetElement).toHaveClass('link');
});

it("should render a child component that doesn't support children", () => {
const { container } = render(
<TestComponent asChild>
<img src="fake.png" />
</TestComponent>,
);
const targetElement = container.firstElementChild as HTMLElement;

// Check node type
expect(targetElement.nodeName).toBe('IMG');
expect(targetElement).toHaveAttribute('src', 'fake.png');
});

it('should render the child element with multiple children', () => {
const { getByText, container } = render(
<AdvancedTestComponent asChild className="test">
<a href="#">Link</a>
</AdvancedTestComponent>,
);
const targetElement = container.firstElementChild as HTMLElement;

expect(getByText('Link')).toBeInTheDocument();
// Check node type
expect(targetElement.nodeName).toBe('A');
expect(targetElement).toHaveClass('test');
expect(targetElement.textContent).toBe('ALinkB');
});

it('should throw an error if more than one child is passed', () => {
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});

render(
<TestComponent asChild>
<a href="#">Link</a>
<a href="#">Link</a>
</TestComponent>,
);

expect(spy).toHaveBeenCalled();
expect(spy.mock.calls[0][0]).toMatch('When using asChild, only one child is allowed');

spy.mockRestore();
});
});
27 changes: 25 additions & 2 deletions src/lib/asChildRenderer/createAsChildTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,42 @@ export function createAsChildTemplate<T extends keyof HTMLElementTagNameMap>(def
const slot = childrenArray[slotIndex];

if (!slot) {
if (process.env.NODE_ENV === 'development') {
if (process.env.NODE_ENV !== 'production') {
console.error('Template: No Slot provided');
}

return null;
}

if (!isValidElement(slot) || !isValidElement(slot.props.children)) {
if (!isValidElement(slot)) {
return null;
}

if (
!isValidElement(slot.props.children) ||
Children.toArray(slot.props.children).length !== 1
) {
if (process.env.NODE_ENV !== 'production') {
console.error('When using asChild, only one child is allowed');
}

return null;
}

if (!isValidElement(slot.props.children)) {
return null;
}

// Render children inside Slot
const nextChildren = [...childrenArray];

if (nextChildren.length === 1 && !slot.props.children.props.children) {
return cloneElement(slot.props.children, {
...mergeProps(rootProps, slot.props.children.props),
ref: composeRefs(ref, slot.props.children.ref),
});
}

// Replace Slot with children
nextChildren[slotIndex] = slot.props.children.props.children;

Expand Down

0 comments on commit 05eb6e3

Please sign in to comment.