Skip to content

Commit 427fcde

Browse files
authored
fix(clerk-react): Fix race condition on updating ClerkProvider props (clerk#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.
1 parent 8bd4802 commit 427fcde

4 files changed

Lines changed: 75 additions & 10 deletions

File tree

.changeset/lemon-bobcats-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@clerk/clerk-react": patch
3+
---
4+
5+
Fix race condition on updating ClerkProvider props before ClerkJS has loaded
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { IsomorphicClerk } from '../isomorphicClerk';
2+
3+
describe('isomorphicClerk', () => {
4+
beforeAll(() => {
5+
jest.useFakeTimers();
6+
});
7+
8+
afterAll(() => {
9+
jest.useRealTimers();
10+
});
11+
12+
it('instantiates a IsomorphicClerk instance', () => {
13+
expect(() => {
14+
new IsomorphicClerk({ publishableKey: 'pk_test_XXX' });
15+
}).not.toThrow();
16+
});
17+
18+
it('updates props asynchronously after clerkjs has loaded', async () => {
19+
const propsHistory: any[] = [];
20+
const dummyClerkJS = {
21+
__unstable__updateProps: (props: any) => propsHistory.push(props),
22+
};
23+
24+
const isomorphicClerk = new IsomorphicClerk({ publishableKey: 'pk_test_XXX' });
25+
(isomorphicClerk as any).clerkjs = dummyClerkJS as any;
26+
27+
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'dark' } });
28+
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'light' } });
29+
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'purple' } });
30+
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'yellow' } });
31+
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'red' } });
32+
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'blue' } });
33+
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'green' } });
34+
expect(propsHistory).toEqual([]);
35+
36+
jest.spyOn(isomorphicClerk, 'loaded', 'get').mockReturnValue(true);
37+
isomorphicClerk.emitLoaded();
38+
void isomorphicClerk.__unstable__updateProps({ appearance: { baseTheme: 'white' } });
39+
await jest.runAllTimersAsync();
40+
41+
expect(propsHistory).toEqual([
42+
{ appearance: { baseTheme: 'dark' } },
43+
{ appearance: { baseTheme: 'light' } },
44+
{ appearance: { baseTheme: 'purple' } },
45+
{ appearance: { baseTheme: 'yellow' } },
46+
{ appearance: { baseTheme: 'red' } },
47+
{ appearance: { baseTheme: 'blue' } },
48+
{ appearance: { baseTheme: 'green' } },
49+
{ appearance: { baseTheme: 'white' } },
50+
]);
51+
});
52+
});

packages/react/src/contexts/ClerkContextProvider.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ const useLoadedIsomorphicClerk = (options: IsomorphicClerkOptions) => {
7272
const isomorphicClerk = React.useMemo(() => IsomorphicClerk.getOrCreateInstance(options), []);
7373

7474
React.useEffect(() => {
75-
isomorphicClerk.__unstable__updateProps({ appearance: options.appearance });
75+
void isomorphicClerk.__unstable__updateProps({ appearance: options.appearance });
7676
}, [options.appearance]);
7777

7878
React.useEffect(() => {
79-
isomorphicClerk.__unstable__updateProps({ options });
79+
void isomorphicClerk.__unstable__updateProps({ options });
8080
}, [options.localization]);
8181

8282
React.useEffect(() => {

packages/react/src/isomorphicClerk.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type IsomorphicLoadedClerk = Without<
8484
| 'buildAfterSignUpUrl'
8585
| 'buildAfterSignInUrl'
8686
| 'buildAfterSignOutUrl'
87+
| 'buildAfterMultiSessionSingleSignOutUrl'
8788
| 'buildUrlWithAuth'
8889
| 'handleRedirectCallback'
8990
| 'handleGoogleOneTapCallback'
@@ -132,6 +133,8 @@ type IsomorphicLoadedClerk = Without<
132133
buildAfterSignUpUrl: () => string | void;
133134
// TODO: Align return type
134135
buildAfterSignOutUrl: () => string | void;
136+
// TODO: Align return type
137+
buildAfterMultiSessionSingleSignOutUrl: () => string | void;
135138
// TODO: Align optional props
136139
mountUserButton: (node: HTMLDivElement, props: UserButtonProps) => void;
137140
mountOrganizationList: (node: HTMLDivElement, props: OrganizationListProps) => void;
@@ -309,6 +312,15 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
309312
}
310313
};
311314

315+
buildAfterMultiSessionSingleSignOutUrl = (): string | void => {
316+
const callback = () => this.clerkjs?.buildAfterMultiSessionSingleSignOutUrl() || '';
317+
if (this.clerkjs && this.#loaded) {
318+
return callback();
319+
} else {
320+
this.premountMethodCalls.set('buildAfterMultiSessionSingleSignOutUrl', callback);
321+
}
322+
};
323+
312324
buildUserProfileUrl = (): string | void => {
313325
const callback = () => this.clerkjs?.buildUserProfileUrl() || '';
314326
if (this.clerkjs && this.#loaded) {
@@ -356,9 +368,6 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
356368

357369
#waitForClerkJS(): Promise<HeadlessBrowserClerk | BrowserClerk> {
358370
return new Promise<HeadlessBrowserClerk | BrowserClerk>(resolve => {
359-
if (this.#loaded) {
360-
resolve(this.clerkjs!);
361-
}
362371
this.addOnLoaded(() => resolve(this.clerkjs!));
363372
});
364373
}
@@ -579,12 +588,11 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk {
579588
}
580589
}
581590

582-
__unstable__updateProps = (props: any): any => {
591+
__unstable__updateProps = async (props: any): Promise<void> => {
592+
const clerkjs = await this.#waitForClerkJS();
583593
// Handle case where accounts has clerk-react@4 installed, but clerk-js@3 is manually loaded
584-
if (this.clerkjs && '__unstable__updateProps' in this.clerkjs) {
585-
(this.clerkjs as any).__unstable__updateProps(props);
586-
} else {
587-
return undefined;
594+
if (clerkjs && '__unstable__updateProps' in clerkjs) {
595+
return (clerkjs as any).__unstable__updateProps(props);
588596
}
589597
};
590598

0 commit comments

Comments
 (0)