Skip to content

Commit

Permalink
Merge branch 'main' into renovate/npm-next-vulnerability
Browse files Browse the repository at this point in the history
  • Loading branch information
nikosdouvlis authored May 23, 2024
2 parents 4622663 + 7f799d8 commit 1bd760a
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/small-frogs-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/nextjs': patch
---

Utilize an awaitable replace function internally to avoid race conditions when using `router.replace`.
10 changes: 6 additions & 4 deletions packages/nextjs/src/app-router/client/ClerkProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { ClerkNextOptionsProvider } from '../../client-boundary/NextOptionsConte
import type { NextClerkProviderProps } from '../../types';
import { ClerkJSScript } from '../../utils/clerk-js-script';
import { mergeNextClerkPropsWithEnv } from '../../utils/mergeNextClerkPropsWithEnv';
import { useAwaitableNavigate } from './useAwaitableNavigate';
import { useAwaitablePush } from './useAwaitablePush';
import { useAwaitableReplace } from './useAwaitableReplace';

declare global {
export interface Window {
Expand All @@ -21,7 +22,8 @@ declare global {
export const ClientClerkProvider = (props: NextClerkProviderProps) => {
const { __unstable_invokeMiddlewareOnAuthStateChange = true, children } = props;
const router = useRouter();
const navigate = useAwaitableNavigate();
const push = useAwaitablePush();
const replace = useAwaitableReplace();
const [isPending, startTransition] = useTransition();

useEffect(() => {
Expand Down Expand Up @@ -68,8 +70,8 @@ export const ClientClerkProvider = (props: NextClerkProviderProps) => {

const mergedProps = mergeNextClerkPropsWithEnv({
...props,
routerPush: navigate,
routerReplace: to => router.replace(to),
routerPush: push,
routerReplace: replace,
});

return (
Expand Down
19 changes: 19 additions & 0 deletions packages/nextjs/src/app-router/client/useAwaitablePush.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client';

import { useRouter } from 'next/navigation';

import { useInternalNavFun } from './useInternalNavFun';

/**
* Creates an "awaitable" navigation function that will do its best effort to wait for Next.js to finish its route transition.
* This is accomplished by wrapping the call to `router.push` in `startTransition()`, which should rely on React to coordinate the pending state. We key off of
* `isPending` to flush the stored promises and ensure the navigates "resolve".
*/
export const useAwaitablePush = () => {
const router = useRouter();

return useInternalNavFun({
windowNav: window?.history.pushState.bind(window.history),
routerNav: router.push.bind(router),
});
};
19 changes: 19 additions & 0 deletions packages/nextjs/src/app-router/client/useAwaitableReplace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client';

import { useRouter } from 'next/navigation';

import { useInternalNavFun } from './useInternalNavFun';

/**
* Creates an "awaitable" navigation function that will do its best effort to wait for Next.js to finish its route transition.
* This is accomplished by wrapping the call to `router.replace` in `startTransition()`, which should rely on React to coordinate the pending state. We key off of
* `isPending` to flush the stored promises and ensure the navigates "resolve".
*/
export const useAwaitableReplace = () => {
const router = useRouter();

return useInternalNavFun({
windowNav: window?.history.replaceState.bind(window.history),
routerNav: router.replace.bind(router),
});
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
'use client';

import { usePathname, useRouter } from 'next/navigation';
import type { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
import { usePathname } from 'next/navigation';
import { useCallback, useEffect, useTransition } from 'react';

import type { NextClerkProviderProps } from '../../types';

declare global {
interface Window {
__clerk_internal_navFun: NonNullable<
NextClerkProviderProps['routerPush'] | NextClerkProviderProps['routerReplace']
>;
__clerk_internal_navPromisesBuffer: Array<() => void> | undefined;
__clerk_internal_navFun: NonNullable<NextClerkProviderProps['routerPush']>;
}
}

/**
* Creates an "awaitable" navigation function that will do its best effort to wait for Next.js to finish its route transition.
* This is accomplished by wrapping the call to `router.push` in `startTransition()`, which should rely on React to coordinate the pending state. We key off of
* `isPending` to flush the stored promises and ensure the navigates "resolve".
*/
export const useAwaitableNavigate = () => {
const router = useRouter();
export const useInternalNavFun = (props: {
windowNav: typeof window.history.pushState | typeof window.history.replaceState;
routerNav: AppRouterInstance['push'] | AppRouterInstance['replace'];
}) => {
const { windowNav, routerNav } = props;
const pathname = usePathname();
const [isPending, startTransition] = useTransition();

Expand All @@ -39,15 +38,15 @@ export const useAwaitableNavigate = () => {
if (opts?.__internal_metadata?.navigationType === 'internal') {
// In 14.1.0, useSearchParams becomes reactive to shallow updates,
// but only if passing `null` as the history state.
// Older versions need to maintain the history state for push to work,
// Older versions need to maintain the history state for push/replace to work,
// without affecting how the Next router works.
const state = ((window as any).next?.version ?? '') < '14.1.0' ? history.state : null;
window.history.pushState(state, '', to);
windowNav(state, '', to);
} else {
// If the navigation is external (usually when navigating away from the component but still within the app),
// we should use the Next.js router to navigate as it will handle updating the URL and also
// fetching the new page if necessary.
router.push(to);
routerNav(to);
}
});
});
Expand Down

0 comments on commit 1bd760a

Please sign in to comment.