Skip to content

Commit

Permalink
fix(clerk-react): Fix race condition on updating ClerkProvider props (#…
Browse files Browse the repository at this point in the history
…3655)

Fixes issues with updating `ClerkProvider` props before ClerkJS has loaded. 

Sometimes, `clerk.__unstable__updateProps` was called before clerkjs had a value, so the props update did not happen. With this change the `__unstable__updateProps` waits for ClerkJS to load and then calls the `clerk.__unstable__updateProps` function to update the props.

This change also implements `buildAfterMultiSessionSingleSignOutUrl` in `isomorphicClerk` to resolve typing issues.
  • Loading branch information
anagstef authored Jul 3, 2024
1 parent 8bd4802 commit 427fcde
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/lemon-bobcats-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@clerk/clerk-react": patch
---

Fix race condition on updating ClerkProvider props before ClerkJS has loaded
52 changes: 52 additions & 0 deletions packages/react/src/__tests__/isomorphicClerk.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { IsomorphicClerk } from '../isomorphicClerk';

describe('isomorphicClerk', () => {
beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});

it('instantiates a IsomorphicClerk instance', () => {
expect(() => {
new IsomorphicClerk({ publishableKey: 'pk_test_XXX' });
}).not.toThrow();
});

it('updates props asynchronously after clerkjs has loaded', async () => {
const propsHistory: any[] = [];
const dummyClerkJS = {
__unstable__updateProps: (props: any) => propsHistory.push(props),
};

const isomorphicClerk = new IsomorphicClerk({ publishableKey: 'pk_test_XXX' });
(isomorphicClerk as any).clerkjs = dummyClerkJS as any;

void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'dark' } });
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'light' } });
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'purple' } });
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'yellow' } });
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'red' } });
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'blue' } });
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'green' } });
expect(propsHistory).toEqual([]);

jest.spyOn(isomorphicClerk, 'loaded', 'get').mockReturnValue(true);
isomorphicClerk.emitLoaded();
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'white' } });
await jest.runAllTimersAsync();

expect(propsHistory).toEqual([
{ appearance: { baseTheme: 'dark' } },
{ appearance: { baseTheme: 'light' } },
{ appearance: { baseTheme: 'purple' } },
{ appearance: { baseTheme: 'yellow' } },
{ appearance: { baseTheme: 'red' } },
{ appearance: { baseTheme: 'blue' } },
{ appearance: { baseTheme: 'green' } },
{ appearance: { baseTheme: 'white' } },
]);
});
});
4 changes: 2 additions & 2 deletions packages/react/src/contexts/ClerkContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ const useLoadedIsomorphicClerk = (options: IsomorphicClerkOptions) => {
const isomorphicClerk = React.useMemo(() => IsomorphicClerk.getOrCreateInstance(options), []);

React.useEffect(() => {
isomorphicClerk.__unstable__updateProps({ appearance: options.appearance });
void isomorphicClerk.__unstable__updateProps({ appearance: options.appearance });
}, [options.appearance]);

React.useEffect(() => {
isomorphicClerk.__unstable__updateProps({ options });
void isomorphicClerk.__unstable__updateProps({ options });
}, [options.localization]);

React.useEffect(() => {
Expand Down
24 changes: 16 additions & 8 deletions packages/react/src/isomorphicClerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type IsomorphicLoadedClerk = Without<
| 'buildAfterSignUpUrl'
| 'buildAfterSignInUrl'
| 'buildAfterSignOutUrl'
| 'buildAfterMultiSessionSingleSignOutUrl'
| 'buildUrlWithAuth'
| 'handleRedirectCallback'
| 'handleGoogleOneTapCallback'
Expand Down Expand Up @@ -132,6 +133,8 @@ type IsomorphicLoadedClerk = Without<
buildAfterSignUpUrl: () => string | void;
// TODO: Align return type
buildAfterSignOutUrl: () => string | void;
// TODO: Align return type
buildAfterMultiSessionSingleSignOutUrl: () => string | void;
// TODO: Align optional props
mountUserButton: (node: HTMLDivElement, props: UserButtonProps) => void;
mountOrganizationList: (node: HTMLDivElement, props: OrganizationListProps) => void;
Expand Down Expand Up @@ -309,6 +312,15 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
}
};

buildAfterMultiSessionSingleSignOutUrl = (): string | void => {
const callback = () => this.clerkjs?.buildAfterMultiSessionSingleSignOutUrl() || '';
if (this.clerkjs && this.#loaded) {
return callback();
} else {
this.premountMethodCalls.set('buildAfterMultiSessionSingleSignOutUrl', callback);
}
};

buildUserProfileUrl = (): string | void => {
const callback = () => this.clerkjs?.buildUserProfileUrl() || '';
if (this.clerkjs && this.#loaded) {
Expand Down Expand Up @@ -356,9 +368,6 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {

#waitForClerkJS(): Promise<HeadlessBrowserClerk | BrowserClerk> {
return new Promise<HeadlessBrowserClerk | BrowserClerk>(resolve => {
if (this.#loaded) {
resolve(this.clerkjs!);
}
this.addOnLoaded(() => resolve(this.clerkjs!));
});
}
Expand Down Expand Up @@ -579,12 +588,11 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
}
}

__unstable__updateProps = (props: any): any => {
__unstable__updateProps = async (props: any): Promise<void> => {
const clerkjs = await this.#waitForClerkJS();
// Handle case where accounts has clerk-react@4 installed, but clerk-js@3 is manually loaded
if (this.clerkjs && '__unstable__updateProps' in this.clerkjs) {
(this.clerkjs as any).__unstable__updateProps(props);
} else {
return undefined;
if (clerkjs && '__unstable__updateProps' in clerkjs) {
return (clerkjs as any).__unstable__updateProps(props);
}
};

Expand Down

0 comments on commit 427fcde

Please sign in to comment.