Skip to content

Commit 097ad4a

Browse files
manovotnyclaude
andauthored
fix(astro): accept single array child in unstyled buttons (#8561)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 166733f commit 097ad4a

3 files changed

Lines changed: 111 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/astro': patch
3+
---
4+
5+
Allow unstyled button components to accept a single React element passed as an array. Fixes a misleading "multiple children" error that could appear when a custom button child crossed a server/client boundary and arrived as a one-item array.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { cleanup, render, screen } from '@testing-library/react';
2+
import { userEvent } from '@testing-library/user-event';
3+
import React from 'react';
4+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
5+
6+
import type * as UtilsModule from '../utils';
7+
8+
const mockRedirectToSignIn = vi.fn();
9+
const originalError = console.error;
10+
11+
const mockClerk = {
12+
redirectToSignIn: mockRedirectToSignIn,
13+
} as any;
14+
15+
vi.mock('../utils', async importActual => {
16+
const actual = await importActual<typeof UtilsModule>();
17+
return {
18+
...actual,
19+
withClerk: (Component: any) => (props: any) => {
20+
return (
21+
<Component
22+
{...props}
23+
clerk={mockClerk}
24+
/>
25+
);
26+
},
27+
};
28+
});
29+
30+
const { SignInButton } = await import('../SignInButton');
31+
32+
describe('<SignInButton/>', () => {
33+
beforeAll(() => {
34+
console.error = vi.fn();
35+
});
36+
37+
afterAll(() => {
38+
console.error = originalError;
39+
});
40+
41+
beforeEach(() => {
42+
mockRedirectToSignIn.mockReset();
43+
});
44+
45+
afterEach(() => {
46+
cleanup();
47+
});
48+
49+
it('renders the default button and calls clerk.redirectToSignIn when clicked', async () => {
50+
render(<SignInButton />);
51+
const btn = screen.getByText('Sign in');
52+
await userEvent.click(btn);
53+
expect(mockRedirectToSignIn).toHaveBeenCalled();
54+
});
55+
56+
it('renders passed button and calls both click handlers', async () => {
57+
const handler = vi.fn();
58+
59+
render(
60+
<SignInButton>
61+
<button
62+
onClick={handler}
63+
type='button'
64+
>
65+
custom button
66+
</button>
67+
</SignInButton>,
68+
);
69+
70+
const btn = screen.getByText('custom button');
71+
await userEvent.click(btn);
72+
73+
expect(handler).toHaveBeenCalled();
74+
expect(mockRedirectToSignIn).toHaveBeenCalled();
75+
});
76+
77+
it('accepts a single child passed as an array', async () => {
78+
const handler = vi.fn();
79+
80+
render(
81+
<SignInButton>
82+
{[
83+
<button
84+
key='custom'
85+
onClick={handler}
86+
type='button'
87+
>
88+
custom button
89+
</button>,
90+
]}
91+
</SignInButton>,
92+
);
93+
94+
const btn = screen.getByText('custom button');
95+
await userEvent.click(btn);
96+
97+
expect(handler).toHaveBeenCalled();
98+
expect(mockRedirectToSignIn).toHaveBeenCalled();
99+
});
100+
});

packages/astro/src/react/utils.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ export const assertSingleChild =
6262
try {
6363
return React.Children.only(children);
6464
} catch {
65+
const childArray = React.Children.toArray(children);
66+
67+
if (childArray.length === 1 && React.isValidElement(childArray[0])) {
68+
return childArray[0];
69+
}
70+
6571
return `You've passed multiple children components to <${name}/>. You can only pass a single child component or text.`;
6672
}
6773
};

0 commit comments

Comments
 (0)