Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/deprecate-signout-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/react": patch
---

Deprecate the `signOutOptions` prop on `<SignOutButton />` in favor of top-level `redirectUrl` and `sessionId` props. The `signOutOptions` prop still works but now emits a deprecation warning.
18 changes: 16 additions & 2 deletions packages/react/src/components/SignOutButton.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { deprecated } from '@clerk/shared/deprecated';
import type { SignOutOptions } from '@clerk/shared/types';
import React from 'react';

Expand All @@ -7,18 +8,31 @@ import { withClerk } from './withClerk';

export type SignOutButtonProps = {
redirectUrl?: string;
sessionId?: string;
/**
* @deprecated Use the `redirectUrl` and `sessionId` props directly instead.
*/
signOutOptions?: SignOutOptions;
children?: React.ReactNode;
};

export const SignOutButton = withClerk(
({ clerk, children, ...props }: React.PropsWithChildren<WithClerkProp<SignOutButtonProps>>) => {
const { redirectUrl = '/', signOutOptions, getContainer, component, ...rest } = props;
const { redirectUrl = '/', sessionId, signOutOptions, getContainer, component, ...rest } = props;

if (signOutOptions) {
deprecated('SignOutButton `signOutOptions`', 'Use the `redirectUrl` and `sessionId` props directly instead.');
}

children = normalizeWithDefaultValue(children, 'Sign out');
const child = assertSingleChild(children)('SignOutButton');

const clickHandler = () => clerk.signOut({ redirectUrl, ...signOutOptions });
const clickHandler = () =>
clerk.signOut({
redirectUrl,
...(sessionId !== undefined && { sessionId }),
...signOutOptions,
});
Comment on lines +21 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Top-level props lose precedence when both APIs are provided (migration bug)

At Line 34, ...signOutOptions is spread after Line 32-33, so deprecated nested values override top-level redirectUrl/sessionId. That keeps the ambiguous behavior this deprecation is meant to eliminate and can sign out/redirect using the wrong target when consumers pass both during migration.

Proposed fix (preserve backward compatibility, make top-level authoritative)
-    const { redirectUrl = '/', sessionId, signOutOptions, getContainer, component, ...rest } = props;
+    const { redirectUrl, sessionId, signOutOptions, getContainer, component, ...rest } = props;
+
+    const resolvedRedirectUrl = redirectUrl ?? signOutOptions?.redirectUrl ?? '/';
+    const resolvedSessionId = sessionId ?? signOutOptions?.sessionId;

@@
     const clickHandler = () =>
       clerk.signOut({
-        redirectUrl,
-        ...(sessionId !== undefined && { sessionId }),
-        ...signOutOptions,
+        redirectUrl: resolvedRedirectUrl,
+        ...(resolvedSessionId !== undefined && { sessionId: resolvedSessionId }),
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/components/SignOutButton.tsx` around lines 21 - 35, The
clickHandler in SignOutButton currently spreads signOutOptions after the
explicit redirectUrl/sessionId so nested values can overwrite top-level props;
change the argument to clerk.signOut so signOutOptions is spread first and then
explicit redirectUrl and sessionId are applied (i.e., spread signOutOptions
before adding redirectUrl and the conditional sessionId) to ensure top-level
redirectUrl/sessionId on the SignOutButton take precedence over deprecated
signOutOptions.

const wrappedChildClickHandler: React.MouseEventHandler = async e => {
await safeExecute((child as any).props.onClick)(e);
return clickHandler();
Expand Down
12 changes: 12 additions & 0 deletions packages/react/src/components/__tests__/SignOutButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ describe('<SignOutButton />', () => {
});
});

it('handles sessionId prop', async () => {
render(<SignOutButton sessionId='sess_1yDceUR8SIKtQ0gIOO8fNsW7nhe' />);
const btn = screen.getByText('Sign out');
await userEvent.click(btn);
await waitFor(() => {
expect(mockSignOut).toHaveBeenCalledWith({
redirectUrl: '/',
sessionId: 'sess_1yDceUR8SIKtQ0gIOO8fNsW7nhe',
});
});
});

it('handles signOutOptions prop', async () => {
render(<SignOutButton signOutOptions={{ redirectUrl: url, sessionId: 'sess_1yDceUR8SIKtQ0gIOO8fNsW7nhe' }} />);
const btn = screen.getByText('Sign out');
Expand Down
Loading