diff --git a/.changeset/cold-chicken-drop.md b/.changeset/cold-chicken-drop.md new file mode 100644 index 00000000000..a1ce6d5e239 --- /dev/null +++ b/.changeset/cold-chicken-drop.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Support remounting ClerkProvider multiple times by making sure that the `updateProps` call during the loading phase does not override any defaults set by `Clerk.load()` for values that are missing diff --git a/integration/templates/react-vite/src/main.tsx b/integration/templates/react-vite/src/main.tsx index ade64d62946..224c68b1496 100644 --- a/integration/templates/react-vite/src/main.tsx +++ b/integration/templates/react-vite/src/main.tsx @@ -15,7 +15,7 @@ const Root = () => { navigate(to)} routerReplace={(to: string) => navigate(to, { replace: true })} > diff --git a/integration/tests/update-props.test.ts b/integration/tests/update-props.test.ts new file mode 100644 index 00000000000..597ec0ba8b1 --- /dev/null +++ b/integration/tests/update-props.test.ts @@ -0,0 +1,45 @@ +import { test } from '@playwright/test'; + +import type { FakeUser } from '../testUtils'; +import { createTestUtils, testAgainstRunningApps } from '../testUtils'; + +testAgainstRunningApps({ withPattern: ['react.vite.withEmailCodes'] })('sign in flow @generic', ({ app }) => { + test.describe.configure({ mode: 'serial' }); + + let fakeUser: FakeUser; + + test.beforeAll(async () => { + const u = createTestUtils({ app }); + fakeUser = u.services.users.createFakeUser({ + fictionalEmail: true, + withPassword: true, + }); + await u.services.users.createBapiUser(fakeUser); + }); + + test.afterAll(async () => { + await fakeUser.deleteIfExists(); + await app.teardown(); + }); + + test('updating props after initial loading does not override defaults set by Clerk.load()', async ({ + page, + context, + }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signIn.goTo({ searchParams: new URLSearchParams({ redirect_url: 'https://www.clerk.com' }) }); + await u.page.waitForFunction(async () => { + // Emulate ClerkProvider being unmounted and mounted again + // as updateProps is going to be called without the default options set by window.Clerk.load() + await (window.Clerk as any).__unstable__updateProps({ options: {} }); + }); + await u.po.signIn.setIdentifier(fakeUser.email); + await u.po.signIn.continue(); + await u.po.signIn.setPassword(fakeUser.password); + await u.po.signIn.continue(); + await u.po.expect.toBeSignedIn(); + // allowedRedirectOrigins should still be respected here + // even after the above updateProps invocation + await u.page.waitForURL(`${app.serverUrl}`); + }); +}); diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx index 2b94fa63e53..26eb42ccac8 100644 --- a/packages/clerk-js/src/ui/Components.tsx +++ b/packages/clerk-js/src/ui/Components.tsx @@ -169,6 +169,7 @@ const Components = (props: ComponentsProps) => { nodes: new Map(), impersonationFab: false, }); + const { googleOneTapModal, signInModal, @@ -215,7 +216,8 @@ const Components = (props: ComponentsProps) => { return; } } - setState(s => ({ ...s, ...restProps })); + + setState(s => ({ ...s, ...restProps, options: { ...s.options, ...restProps.options } })); }; componentsControls.closeModal = name => {