From 9a1fe37289c7606dc111913cb9f70f2a2efff6b7 Mon Sep 17 00:00:00 2001 From: George Desipris <73396808+desiprisg@users.noreply.github.com> Date: Tue, 28 Nov 2023 14:36:38 +0200 Subject: [PATCH] fix(clerk-js): Add push/replace props & replace state when merging fragment into url (#1304) * feat(repo): Add push and replace props to ClerkProvider * chore(repo): Add integration test for path routing on history back * fix(repo): Fix expectation in sign in page objects to be more resilient --------- Co-authored-by: Clerk Cookie <136073014+clerk-cookie@users.noreply.github.com> Co-authored-by: Dimitris Klouvas --- .changeset/gold-islands-cover.md | 5 ++ .changeset/smart-suns-train.md | 9 +++ .changeset/stupid-suits-accept.md | 5 ++ integration/templates/react-vite/src/main.tsx | 3 +- integration/testUtils/signInPageObject.ts | 7 ++- integration/tests/navigation.test.ts | 14 +++++ packages/chrome-extension/README.md | 3 +- .../clerk-js/src/core/clerk.redirects.test.ts | 16 ++--- packages/clerk-js/src/core/clerk.test.ts | 58 +++++++++---------- packages/clerk-js/src/core/clerk.ts | 6 +- .../clerk-js/src/ui/router/PathRouter.tsx | 7 ++- .../src/GatsbyClerkProvider.tsx | 3 +- .../src/app-router/client/ClerkProvider.tsx | 6 +- .../app-router/client/useAwaitableNavigate.ts | 13 +++-- packages/nextjs/src/pages/ClerkProvider.tsx | 5 +- packages/remix/src/client/ClerkApp.tsx | 4 +- .../remix/src/client/RemixClerkProvider.tsx | 3 +- .../remix/src/client/useAwaitableNavigate.tsx | 5 +- packages/types/src/clerk.ts | 9 ++- playground/vite-react-ts/src/App.tsx | 11 ++-- 20 files changed, 126 insertions(+), 66 deletions(-) create mode 100644 .changeset/gold-islands-cover.md create mode 100644 .changeset/smart-suns-train.md create mode 100644 .changeset/stupid-suits-accept.md diff --git a/.changeset/gold-islands-cover.md b/.changeset/gold-islands-cover.md new file mode 100644 index 0000000000..0219bff0e5 --- /dev/null +++ b/.changeset/gold-islands-cover.md @@ -0,0 +1,5 @@ +--- +'@clerk/nextjs': major +--- + +Fix a bug where navigating from the sign in page to the sign up page required two back button presses to go back. diff --git a/.changeset/smart-suns-train.md b/.changeset/smart-suns-train.md new file mode 100644 index 0000000000..f2d5e5e677 --- /dev/null +++ b/.changeset/smart-suns-train.md @@ -0,0 +1,9 @@ +--- +'gatsby-plugin-clerk': major +'@clerk/clerk-js': major +'@clerk/nextjs': major +'@clerk/remix': major +'@clerk/types': major +--- + +Use the new `routerPush` and `routerReplace` props for `` instead of `navigate`. diff --git a/.changeset/stupid-suits-accept.md b/.changeset/stupid-suits-accept.md new file mode 100644 index 0000000000..3b5defa12f --- /dev/null +++ b/.changeset/stupid-suits-accept.md @@ -0,0 +1,5 @@ +--- +'@clerk/types': major +--- + +Introduces two new props for ``, `push` and `replace`. These props replace the `navigate` prop. Passing both `push` and `replace` will allow Clerk to correctly handle navigations without causing issues with the host application's router. \ No newline at end of file diff --git a/integration/templates/react-vite/src/main.tsx b/integration/templates/react-vite/src/main.tsx index 4609a674b3..ade64d6294 100644 --- a/integration/templates/react-vite/src/main.tsx +++ b/integration/templates/react-vite/src/main.tsx @@ -16,7 +16,8 @@ const Root = () => { // @ts-ignore publishableKey={import.meta.env.VITE_CLERK_PUBLISHABLE_KEY as string} clerkJSUrl={import.meta.env.VITE_CLERK_JS as string} - navigate={(to: string) => navigate(to)} + routerPush={(to: string) => navigate(to)} + routerReplace={(to: string) => navigate(to, { replace: true })} > diff --git a/integration/testUtils/signInPageObject.ts b/integration/testUtils/signInPageObject.ts index 588502572f..adba094ab0 100644 --- a/integration/testUtils/signInPageObject.ts +++ b/integration/testUtils/signInPageObject.ts @@ -23,8 +23,8 @@ export const createSignInComponentPageObject = (testArgs: TestArgs) => { }, setInstantPassword: async (val: string) => { const passField = self.getPasswordInput(); - await passField.fill(val, { force: true }); await expect(passField).toBeVisible(); + await passField.fill(val, { force: true }); }, getGoToSignUp: () => { return page.getByRole('link', { name: /sign up/i }); @@ -42,7 +42,10 @@ export const createSignInComponentPageObject = (testArgs: TestArgs) => { return page.getByRole('button', { name: new RegExp(`continue with ${provider}`, 'gi') }); }, signInWithEmailAndInstantPassword: async (opts: { email: string; password: string }) => { - await self.getIdentifierInput().fill(opts.email); + const identifierField = self.getIdentifierInput(); + await expect(identifierField).toBeVisible(); + + await identifierField.fill(opts.email); await self.setInstantPassword(opts.password); await self.continue(); }, diff --git a/integration/tests/navigation.test.ts b/integration/tests/navigation.test.ts index 637d2b9a81..e86436c434 100644 --- a/integration/tests/navigation.test.ts +++ b/integration/tests/navigation.test.ts @@ -197,4 +197,18 @@ export default function Page() { await u.page.getByText(/Add an email address/i).click(); await u.page.getByText(/Cancel/i).click(); }); + + test('sign in with path routing navigates to previous page', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + await u.po.signIn.goTo(); + await u.po.signIn.waitForMounted(); + + await u.po.signIn.getGoToSignUp().click(); + await u.po.signUp.waitForMounted(); + await u.page.waitForURL(`${app.serverUrl}/sign-up?redirect_url=${encodeURIComponent(app.serverUrl + '/')}`); + + await page.goBack(); + await u.po.signIn.waitForMounted(); + await u.page.waitForURL(`${app.serverUrl}/sign-in`); + }); }); diff --git a/packages/chrome-extension/README.md b/packages/chrome-extension/README.md index 6d24b96906..bae2603781 100644 --- a/packages/chrome-extension/README.md +++ b/packages/chrome-extension/README.md @@ -68,7 +68,8 @@ function ClerkProviderWithRoutes() { return ( navigate(to)} + routerPush={to => navigate(to)} + routerReplace={to => navigate(to, { replace: true })} > { clerkForProductionInstance = new Clerk(productionPublishableKey); await clerkForProductionInstance.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); clerkForDevelopmentInstance = new Clerk(developmentPublishableKey); await clerkForDevelopmentInstance.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); }); @@ -193,12 +193,12 @@ describe('Clerk singleton - Redirects', () => { clerkForProductionInstance = new Clerk(productionPublishableKey); await clerkForProductionInstance.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); clerkForDevelopmentInstance = new Clerk(developmentPublishableKey); await clerkForDevelopmentInstance.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); }); @@ -284,12 +284,12 @@ describe('Clerk singleton - Redirects', () => { clerkForProductionInstance = new Clerk(productionPublishableKey); await clerkForProductionInstance.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); clerkForDevelopmentInstance = new Clerk(developmentPublishableKey); await clerkForDevelopmentInstance.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); }); @@ -317,12 +317,12 @@ describe('Clerk singleton - Redirects', () => { clerkForProductionInstance = new Clerk(productionPublishableKey); await clerkForProductionInstance.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); clerkForDevelopmentInstance = new Clerk(developmentPublishableKey); await clerkForDevelopmentInstance.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); }); diff --git a/packages/clerk-js/src/core/clerk.test.ts b/packages/clerk-js/src/core/clerk.test.ts index 2942535895..832ef69c00 100644 --- a/packages/clerk-js/src/core/clerk.test.ts +++ b/packages/clerk-js/src/core/clerk.test.ts @@ -529,14 +529,14 @@ describe('Clerk singleton', () => { }); it('uses window location if a custom navigate is defined but destination has different origin', async () => { - await sut.load({ navigate: mockNavigate }); + await sut.load({ routerPush: mockNavigate }); const toUrl = 'https://www.origindifferent.com/'; await sut.navigate(toUrl); expect(mockHref).toHaveBeenCalledWith(toUrl); }); it('wraps custom navigate method in a promise if provided and it sync', async () => { - await sut.load({ navigate: mockNavigate }); + await sut.load({ routerPush: mockNavigate }); const toUrl = 'http://test.host/path#hash'; const res = sut.navigate(toUrl); expect(res.then).toBeDefined(); @@ -596,7 +596,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); if (!sut.client) { fail('we should always have a client'); @@ -661,7 +661,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); if (!sut.client) { fail('we should always have a client'); @@ -729,7 +729,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); if (!sut.client) { fail('we should always have a client'); @@ -787,7 +787,7 @@ describe('Clerk singleton', () => { const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive; @@ -839,7 +839,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); if (!sut.client) { fail('we should always have a client'); @@ -887,7 +887,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); await sut.handleRedirectCallback(); @@ -930,7 +930,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.handleRedirectCallback({ @@ -986,7 +986,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive as any; @@ -1043,7 +1043,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive as any; @@ -1097,7 +1097,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); await sut.handleRedirectCallback(); @@ -1147,7 +1147,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); await sut.handleRedirectCallback(); @@ -1191,7 +1191,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); await sut.handleRedirectCallback(); @@ -1240,7 +1240,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); await sut.handleRedirectCallback(); @@ -1274,7 +1274,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); await sut.handleRedirectCallback(); @@ -1320,7 +1320,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); if (!sut.client) { fail('we should always have a client'); @@ -1381,7 +1381,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); if (!sut.client) { fail('we should always have a client'); @@ -1434,7 +1434,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); if (!sut.client) { fail('we should always have a client'); @@ -1474,7 +1474,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); if (!sut.client) { fail('we should always have a client'); @@ -1515,7 +1515,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive; @@ -1546,7 +1546,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive; @@ -1579,7 +1579,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive; @@ -1610,7 +1610,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive; @@ -1637,7 +1637,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive; @@ -1661,7 +1661,7 @@ describe('Clerk singleton', () => { const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive; @@ -1687,7 +1687,7 @@ describe('Clerk singleton', () => { const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive; const res = { ping: 'ping' }; @@ -1712,7 +1712,7 @@ describe('Clerk singleton', () => { const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive; await expect(async () => { @@ -1739,7 +1739,7 @@ describe('Clerk singleton', () => { const mockSetActive = jest.fn(); const sut = new Clerk(productionPublishableKey); await sut.load({ - navigate: mockNavigate, + routerPush: mockNavigate, }); sut.setActive = mockSetActive; diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 5af59e33a3..d75ef10043 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -31,6 +31,7 @@ import type { HandleOAuthCallbackParams, InstanceType, ListenerCallback, + NavigateOptions, OrganizationListProps, OrganizationProfileProps, OrganizationResource, @@ -665,13 +666,14 @@ export class Clerk implements ClerkInterface { return unsubscribe; }; - public navigate = async (to: string | undefined): Promise => { + public navigate = async (to: string | undefined, options?: NavigateOptions): Promise => { if (!to || !inBrowser()) { return; } const toURL = new URL(to, window.location.href); - const customNavigate = this.#options.navigate; + const customNavigate = + options?.replace && this.#options.routerReplace ? this.#options.routerReplace : this.#options.routerPush; if (toURL.origin !== window.location.origin || !customNavigate) { windowNavigate(toURL); diff --git a/packages/clerk-js/src/ui/router/PathRouter.tsx b/packages/clerk-js/src/ui/router/PathRouter.tsx index bbb951e035..c9211cd158 100644 --- a/packages/clerk-js/src/ui/router/PathRouter.tsx +++ b/packages/clerk-js/src/ui/router/PathRouter.tsx @@ -1,3 +1,4 @@ +import type { NavigateOptions } from '@clerk/types'; import React from 'react'; import { hasUrlInFragment, mergeFragmentIntoUrl, stripOrigin } from '../../utils'; @@ -18,12 +19,12 @@ export const PathRouter = ({ basePath, preservedParams, children }: PathRouterPr throw new Error('Clerk: Missing navigate option.'); } - const internalNavigate = (toURL: URL | string | undefined) => { + const internalNavigate = (toURL: URL | string | undefined, options?: NavigateOptions) => { if (!toURL) { return; } // Only send the path - return navigate(stripOrigin(toURL)); + return navigate(stripOrigin(toURL), options); }; const getPath = () => { @@ -38,7 +39,7 @@ export const PathRouter = ({ basePath, preservedParams, children }: PathRouterPr const convertHashToPath = async () => { if (hasUrlInFragment(window.location.hash)) { const url = mergeFragmentIntoUrl(new URL(window.location.href)); - await internalNavigate(url.href); + await internalNavigate(url.href, { replace: true }); setStripped(true); } }; diff --git a/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx b/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx index ca132a26c1..e36a9c982d 100644 --- a/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx +++ b/packages/gatsby-plugin-clerk/src/GatsbyClerkProvider.tsx @@ -21,7 +21,8 @@ export function ClerkProvider({ children, ...rest }: GatsbyClerkProviderProps) { return ( navigate(to)} + routerPush={to => navigate(to)} + routerReplace={to => navigate(to, { replace: true })} initialState={__clerk_ssr_state || {}} sdkMetadata={SDK_METADATA} telemetry={ diff --git a/packages/nextjs/src/app-router/client/ClerkProvider.tsx b/packages/nextjs/src/app-router/client/ClerkProvider.tsx index 8fe3ca193a..2a0722adf3 100644 --- a/packages/nextjs/src/app-router/client/ClerkProvider.tsx +++ b/packages/nextjs/src/app-router/client/ClerkProvider.tsx @@ -34,7 +34,11 @@ export const ClientClerkProvider = (props: NextClerkProviderProps) => { }; }, []); - const mergedProps = mergeNextClerkPropsWithEnv({ ...props, navigate }); + const mergedProps = mergeNextClerkPropsWithEnv({ + ...props, + routerPush: navigate, + routerReplace: to => router.replace(to), + }); return ( {/*// @ts-ignore*/} diff --git a/packages/nextjs/src/app-router/client/useAwaitableNavigate.ts b/packages/nextjs/src/app-router/client/useAwaitableNavigate.ts index 19330b6528..f1f45509ff 100644 --- a/packages/nextjs/src/app-router/client/useAwaitableNavigate.ts +++ b/packages/nextjs/src/app-router/client/useAwaitableNavigate.ts @@ -3,9 +3,11 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { useCallback, useEffect } from 'react'; +type NavigateFunction = ReturnType['push']; + declare global { interface Window { - __clerk_nav_ref: (to: string) => any; + __clerk_nav_ref: NavigateFunction; __clerk_nav_resolves_ref: Array<(val?: any) => any> | undefined; } } @@ -19,9 +21,10 @@ export const useAwaitableNavigate = () => { const urlKey = pathname + params.toString(); useEffect(() => { - window.__clerk_nav_ref = (to: string) => { + // eslint-disable-next-line @typescript-eslint/no-misused-promises + window.__clerk_nav_ref = (to, opts) => { if (to === window.location.href.replace(window.location.origin, '')) { - push(to); + push(to, opts); return Promise.resolve(); } @@ -43,7 +46,7 @@ export const useAwaitableNavigate = () => { window.__clerk_nav_resolves_ref = []; }); - return useCallback((to: string) => { - return window.__clerk_nav_ref(to); + return useCallback((to, opts) => { + return window.__clerk_nav_ref(to, opts); }, []); }; diff --git a/packages/nextjs/src/pages/ClerkProvider.tsx b/packages/nextjs/src/pages/ClerkProvider.tsx index ae574c9fad..78444777c2 100644 --- a/packages/nextjs/src/pages/ClerkProvider.tsx +++ b/packages/nextjs/src/pages/ClerkProvider.tsx @@ -12,7 +12,7 @@ __internal__setErrorThrowerOptions({ packageName: '@clerk/nextjs' }); export function ClerkProvider({ children, ...props }: NextClerkProviderProps): JSX.Element { const { __unstable_invokeMiddlewareOnAuthStateChange = true } = props; - const { push } = useRouter(); + const { push, replace } = useRouter(); ReactClerkProvider.displayName = 'ReactClerkProvider'; useSafeLayoutEffect(() => { @@ -31,7 +31,8 @@ export function ClerkProvider({ children, ...props }: NextClerkProviderProps): J }, []); const navigate = (to: string) => push(to); - const mergedProps = mergeNextClerkPropsWithEnv({ ...props, navigate }); + const replaceNavigate = (to: string) => replace(to); + const mergedProps = mergeNextClerkPropsWithEnv({ ...props, routerPush: navigate, routerReplace: replaceNavigate }); // ClerkProvider automatically injects __clerk_ssr_state // getAuth returns a user-facing authServerSideProps that hides __clerk_ssr_state // @ts-expect-error initialState is hidden from the types as it's a private prop diff --git a/packages/remix/src/client/ClerkApp.tsx b/packages/remix/src/client/ClerkApp.tsx index 23dbd5295d..fdebc0d516 100644 --- a/packages/remix/src/client/ClerkApp.tsx +++ b/packages/remix/src/client/ClerkApp.tsx @@ -4,7 +4,9 @@ import React from 'react'; import type { RemixClerkProviderProps } from './RemixClerkProvider'; import { ClerkProvider } from './RemixClerkProvider'; -type ClerkAppOptions = Partial>; +type ClerkAppOptions = Partial< + Omit +>; export function ClerkApp(App: () => JSX.Element, opts: ClerkAppOptions = {}) { return () => { diff --git a/packages/remix/src/client/RemixClerkProvider.tsx b/packages/remix/src/client/RemixClerkProvider.tsx index 8b56beed4e..effceea128 100644 --- a/packages/remix/src/client/RemixClerkProvider.tsx +++ b/packages/remix/src/client/RemixClerkProvider.tsx @@ -89,7 +89,8 @@ export function ClerkProvider({ children, ...rest }: RemixClerkProviderProps): J return ( awaitableNavigateRef.current?.(to)} + routerPush={(to: string) => awaitableNavigateRef.current?.(to)} + routerReplace={(to: string) => awaitableNavigateRef.current?.(to, { replace: true })} initialState={__clerk_ssr_state} sdkMetadata={SDK_METADATA} {...mergedProps} diff --git a/packages/remix/src/client/useAwaitableNavigate.tsx b/packages/remix/src/client/useAwaitableNavigate.tsx index 3998e664c5..ec82716f5d 100644 --- a/packages/remix/src/client/useAwaitableNavigate.tsx +++ b/packages/remix/src/client/useAwaitableNavigate.tsx @@ -1,5 +1,6 @@ import { useLocation, useNavigate } from '@remix-run/react'; import React from 'react'; +import type { NavigateOptions } from 'react-router'; type Resolve = (value?: unknown) => void; @@ -18,10 +19,10 @@ export const useAwaitableNavigate = () => { resolveAll(); }, [location]); - return (to: string) => { + return (to: string, opts?: NavigateOptions) => { return new Promise(res => { resolveFunctionsRef.current.push(res); - navigate(to); + navigate(to, opts); }); }; }; diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 8392899c7e..578d387d8e 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -498,14 +498,15 @@ export type BuildUrlWithAuthParams = { // TODO: Make sure Isomorphic Clerk navigate can work with the correct type: // (to: string) => Promise -export type CustomNavigation = (to: string) => Promise | void; +export type CustomNavigation = (to: string, options?: NavigateOptions) => Promise | void; export type ClerkThemeOptions = DeepSnakeToCamel>; export interface ClerkOptions { appearance?: Appearance; localization?: LocalizationResource; - navigate?: (to: string) => Promise | unknown; + routerPush?: (to: string) => Promise | unknown; + routerReplace?: (to: string) => Promise | unknown; polling?: boolean; selectInitialSession?: (client: ClientResource) => ActiveSessionResource | null; /** Controls if ClerkJS will load with the standard browser setup using Clerk cookies */ @@ -539,6 +540,10 @@ export interface ClerkOptions { sdkMetadata?: SDKMetadata; } +export interface NavigateOptions { + replace?: boolean; +} + export interface Resources { client: ClientResource; session?: ActiveSessionResource | null; diff --git a/playground/vite-react-ts/src/App.tsx b/playground/vite-react-ts/src/App.tsx index a2d123ef80..5eb6e00147 100644 --- a/playground/vite-react-ts/src/App.tsx +++ b/playground/vite-react-ts/src/App.tsx @@ -1,7 +1,3 @@ -import reactLogo from './assets/react.svg'; -import viteLogo from '/vite.svg'; -import clerkLogo from '/clerk.svg'; -import './App.css'; import { ClerkProvider, RedirectToSignIn, @@ -14,6 +10,10 @@ import { useUser, } from '@clerk/clerk-react'; import { BrowserRouter, Route, Routes, useNavigate } from 'react-router-dom'; +import './App.css'; +import reactLogo from './assets/react.svg'; +import clerkLogo from '/clerk.svg'; +import viteLogo from '/vite.svg'; const clerkPubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY; @@ -67,7 +67,8 @@ function ClerkProviderWithRoutes() { return ( navigate(to)} + routerPush={to => navigate(to)} + routerReplace={to => navigate(to, {replace: true} )} >