Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-ladybugs-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/nextjs': patch
---

Fix shallow internal-component navigation.
2 changes: 2 additions & 0 deletions packages/nextjs/src/app-router/client/ClerkProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ const NextClientClerkProvider = (props: NextClerkProviderProps) => {

const mergedProps = mergeNextClerkPropsWithEnv({
...props,
// @ts-expect-error Error because of the stricter types of internal `push`
routerPush: push,
// @ts-expect-error Error because of the stricter types of internal `replace`
routerReplace: replace,
});

Expand Down
6 changes: 3 additions & 3 deletions packages/nextjs/src/app-router/client/useInternalNavFun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const useInternalNavFun = (props: {
windowNav: typeof window.history.pushState | typeof window.history.replaceState | undefined;
routerNav: AppRouterInstance['push'] | AppRouterInstance['replace'];
name: string;
}) => {
}): NavigationFunction => {
const { windowNav, routerNav, name } = props;
const pathname = usePathname();
const [isPending, startTransition] = useTransition();
Expand Down Expand Up @@ -68,8 +68,8 @@ export const useInternalNavFun = (props: {
}
}, [pathname, isPending]);

return useCallback((to: string) => {
return getClerkNavigationObject(name).fun(to);
return useCallback<NavigationFunction>((to, metadata) => {
return getClerkNavigationObject(name).fun(to, metadata);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mind sharing an instance of where we were passing the second argument metadata to push/replace? I want to look into why that wasn't causing a typescript error. useAwaitableReplace/useAwaitablePush should have only accepted a single argument.

Copy link
Member Author

@panteliselef panteliselef Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useAwaitableReplace/useAwaitablePush should have only accepted a single argument.

the hooks themselves do not take any arguments, previously they were returning a fuction that would accept a single argument, but in Clerk.navigate we are clearly passing 2 arguments.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was not giving any typescript errors because the 2nd argument is optional

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript is inferring the type as const useAwaitablePush: () => (to: string) => unknown though, so the returned function should be typed as only accepting a single argument. I guess we're losing that type safety somewhere along the way 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahhh okay clerk-js accepts that as RouterFn which has the optional metadata argument. Since the previous type was (to: string) => unknown, that satisfies (to: string, metadata?: whatever) => void.

// We are not expecting name to change
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand Down
16 changes: 13 additions & 3 deletions packages/nextjs/src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@ declare namespace NodeJS {
}
}

// eslint-disable-next-line @typescript-eslint/consistent-type-imports
type NextClerkProviderProps = import('./types').NextClerkProviderProps;
type RequireMetadata<T extends (to: any, metadata?: any) => any> = T extends (
to: infer To,
metadata?: infer Metadata,
) => infer R
? (to: To, metadata: Metadata) => R
: never;

type NavigationFunction =
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
| RequireMetadata<NonNullable<import('./types').NextClerkProviderProps['routerPush']>>
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
| RequireMetadata<NonNullable<import('./types').NextClerkProviderProps['routerReplace']>>;

interface Window {
__clerk_internal_navigations: Record<
string,
{
fun: NonNullable<NextClerkProviderProps['routerPush'] | NextClerkProviderProps['routerReplace']>;
fun: NavigationFunction;
promisesBuffer: Array<() => void> | undefined;
}
>;
Expand Down
4 changes: 1 addition & 3 deletions packages/types/src/snapshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ import type {
import type { OrganizationSettingsJSON } from './organizationSettings';
import type { SignInJSON } from './signIn';
import type { UserSettingsJSON } from './userSettings';
import type { Nullable } from './utils';

type Override<T, U> = Omit<T, keyof U> & U;
import type { Nullable, Override } from './utils';

export type SignInJSONSnapshot = Override<
Nullable<SignInJSON, 'status' | 'identifier' | 'supported_first_factors' | 'supported_second_factors'>,
Expand Down
8 changes: 8 additions & 0 deletions packages/types/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,11 @@ export type Autocomplete<U extends T, T = string> = U | (T & Record<never, never
export type Without<T, W> = {
[P in keyof T as Exclude<P, W>]: T[P];
};

/**
* Overrides the type of existing properties
* const obj = { a: string, b: number } as const;
* type Value = Override<typeof obj, { b: string }>
* Value contains: { a:string, b: string }
*/
export type Override<T, U> = Omit<T, keyof U> & U;