Skip to content

Commit c5bd714

Browse files
authored
fix(nextjs): Use separate buffers for each awaitable navigation type (#3480)
1 parent 3c2b666 commit c5bd714

File tree

5 files changed

+34
-16
lines changed

5 files changed

+34
-16
lines changed

.changeset/three-eels-battle.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/nextjs': patch
3+
---
4+
5+
Bug fix: Correctly update history state when on internal navigations.

integration/tests/navigation.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ testAgainstRunningApps({ withPattern: ['next.appRouter.withEmailCodes'] })('navi
4848
await u.po.expect.toBeSignedIn();
4949
});
5050

51-
test.skip('sign in with path routing navigates to previous page', async ({ page, context }) => {
51+
test('sign in with path routing navigates to previous page', async ({ page, context }) => {
5252
const u = createTestUtils({ app, page, context });
5353
await u.po.signIn.goTo();
5454
await u.po.signIn.waitForMounted();

packages/nextjs/src/app-router/client/useAwaitablePush.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ export const useAwaitablePush = () => {
1515
return useInternalNavFun({
1616
windowNav: typeof window !== 'undefined' ? window.history.pushState.bind(window.history) : undefined,
1717
routerNav: router.push.bind(router),
18+
name: 'push',
1819
});
1920
};

packages/nextjs/src/app-router/client/useAwaitableReplace.ts

+1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ export const useAwaitableReplace = () => {
1515
return useInternalNavFun({
1616
windowNav: typeof window !== 'undefined' ? window.history.replaceState.bind(window.history) : undefined,
1717
routerNav: router.replace.bind(router),
18+
name: 'replace',
1819
});
1920
};

packages/nextjs/src/app-router/client/useInternalNavFun.ts

+26-15
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,40 @@ import type { NextClerkProviderProps } from '../../types';
66

77
declare global {
88
interface Window {
9-
__clerk_internal_navFun: NonNullable<
10-
NextClerkProviderProps['routerPush'] | NextClerkProviderProps['routerReplace']
9+
__clerk_internal_navigations: Record<
10+
string,
11+
{
12+
fun: NonNullable<NextClerkProviderProps['routerPush'] | NextClerkProviderProps['routerReplace']>;
13+
promisesBuffer: Array<() => void> | undefined;
14+
}
1115
>;
12-
__clerk_internal_navPromisesBuffer: Array<() => void> | undefined;
1316
}
1417
}
1518

19+
const getClerkNavigationObject = (name: string) => {
20+
window.__clerk_internal_navigations ??= {};
21+
// @ts-ignore
22+
window.__clerk_internal_navigations[name] ??= {};
23+
return window.__clerk_internal_navigations[name];
24+
};
25+
1626
export const useInternalNavFun = (props: {
1727
windowNav: typeof window.history.pushState | typeof window.history.replaceState | undefined;
1828
routerNav: AppRouterInstance['push'] | AppRouterInstance['replace'];
29+
name: string;
1930
}) => {
20-
const { windowNav, routerNav } = props;
31+
const { windowNav, routerNav, name } = props;
2132
const pathname = usePathname();
2233
const [isPending, startTransition] = useTransition();
2334

2435
if (windowNav) {
25-
window.__clerk_internal_navFun = (to, opts) => {
36+
getClerkNavigationObject(name).fun = (to, opts) => {
2637
return new Promise<void>(res => {
27-
if (!window.__clerk_internal_navPromisesBuffer) {
28-
// We need to use window to store the reference to the buffer,
29-
// as ClerkProvider might be unmounted and remounted during navigations
30-
// If we use a ref, it will be reset when ClerkProvider is unmounted
31-
window.__clerk_internal_navPromisesBuffer = [];
32-
}
33-
window.__clerk_internal_navPromisesBuffer.push(res);
38+
// We need to use window to store the reference to the buffer,
39+
// as ClerkProvider might be unmounted and remounted during navigations
40+
// If we use a ref, it will be reset when ClerkProvider is unmounted
41+
getClerkNavigationObject(name).promisesBuffer ??= [];
42+
getClerkNavigationObject(name).promisesBuffer?.push(res);
3443
startTransition(() => {
3544
// If the navigation is internal, we should use the history API to navigate
3645
// as this is the way to perform a shallow navigation in Next.js App Router
@@ -54,8 +63,8 @@ export const useInternalNavFun = (props: {
5463
}
5564

5665
const flushPromises = () => {
57-
window.__clerk_internal_navPromisesBuffer?.forEach(resolve => resolve());
58-
window.__clerk_internal_navPromisesBuffer = [];
66+
getClerkNavigationObject(name).promisesBuffer?.forEach(resolve => resolve());
67+
getClerkNavigationObject(name).promisesBuffer = [];
5968
};
6069

6170
// Flush any pending promises on mount/unmount
@@ -72,6 +81,8 @@ export const useInternalNavFun = (props: {
7281
}, [pathname, isPending]);
7382

7483
return useCallback((to: string) => {
75-
return window.__clerk_internal_navFun(to);
84+
return getClerkNavigationObject(name).fun(to);
85+
// We are not expecting name to change
86+
// eslint-disable-next-line react-hooks/exhaustive-deps
7687
}, []);
7788
};

0 commit comments

Comments
 (0)