From 0eba8a7d59014d59f19ee4e1bf2859968ff2b961 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 21 Nov 2025 19:29:36 +0200 Subject: [PATCH 1/5] chore(shared,clerk-js): Set defaultOptions to queryClient --- packages/clerk-js/package.json | 2 +- packages/clerk-js/src/core/clerk.ts | 56 ++++++++++++++----- packages/clerk-js/src/test/mock-helpers.ts | 3 + packages/shared/package.json | 2 +- .../react/clerk-rq/use-clerk-query-client.ts | 47 +++++++++++----- pnpm-lock.yaml | 8 ++- pnpm-workspace.yaml | 4 ++ 7 files changed, 89 insertions(+), 33 deletions(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index cd8ad69e3cb..ff07532f5b6 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -71,7 +71,7 @@ "@formkit/auto-animate": "^0.8.2", "@stripe/stripe-js": "5.6.0", "@swc/helpers": "^0.5.17", - "@tanstack/query-core": "5.87.4", + "@tanstack/query-core": "catalog:tanstack", "@zxcvbn-ts/core": "3.0.4", "@zxcvbn-ts/language-common": "3.0.4", "alien-signals": "2.0.6", diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 6353d4c09d3..6e2c669d250 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -94,7 +94,7 @@ import type { } from '@clerk/shared/types'; import { addClerkPrefix, isAbsoluteUrl, stripScheme } from '@clerk/shared/url'; import { allSettled, handleValueOrFn, noop } from '@clerk/shared/utils'; -import type { QueryClient } from '@tanstack/query-core'; +import type { QueryClient, QueryClientConfig } from '@tanstack/query-core'; import { debugLogger, initDebugLogger } from '@/utils/debug'; @@ -198,6 +198,24 @@ const defaultOptions: ClerkOptions = { newSubscriptionRedirectUrl: undefined, }; +const RQ_CLIENT_TAG = 'clerk-rq-client' as const; + +type ClerkRQClient = { __tag: typeof RQ_CLIENT_TAG; client: QueryClient }; + +const clerkQueryClientConfig: QueryClientConfig = { + defaultOptions: { + queries: { + // use the retry logic that fapiClient uses + retry: false, + // Note: to refetch onWindowFocus, you need to call `queryClient.mount()` + refetchOnWindowFocus: false, + refetchOnReconnect: false, + // the query will refetch on mount if the data is stale + refetchOnMount: true, + }, + }, +}; + export class Clerk implements ClerkInterface { public static mountComponentRenderer?: MountComponentRenderer; @@ -243,23 +261,12 @@ export class Clerk implements ClerkInterface { #touchThrottledUntil = 0; #publicEventBus = createClerkEventBus(); - get __internal_queryClient(): { __tag: 'clerk-rq-client'; client: QueryClient } | undefined { - if (!this.#queryClient) { - void import('./query-core') - .then(module => module.QueryClient) - .then(QueryClient => { - if (this.#queryClient) { - return; - } - this.#queryClient = new QueryClient(); - // @ts-expect-error - queryClientStatus is not typed - this.#publicEventBus.emit('queryClientStatus', 'ready'); - }); - } + get __internal_queryClient(): ClerkRQClient | undefined { + this.#initQueryClient(); return this.#queryClient ? { - __tag: 'clerk-rq-client', + __tag: RQ_CLIENT_TAG, client: this.#queryClient, } : undefined; @@ -289,6 +296,25 @@ export class Clerk implements ClerkInterface { public __internal_setActiveInProgress = false; + #initQueryClient = (): void => { + if (this.#queryClient) { + return; + } + + void import('./query-core') + .then(module => module.QueryClient) + .then(QueryClientCtor => { + if (this.#queryClient) { + return; + } + + this.#queryClient = new QueryClientCtor(clerkQueryClientConfig); + + // @ts-expect-error - queryClientStatus is not typed + this.#publicEventBus.emit('queryClientStatus', 'ready'); + }); + }; + get publishableKey(): string { return this.#publishableKey; } diff --git a/packages/clerk-js/src/test/mock-helpers.ts b/packages/clerk-js/src/test/mock-helpers.ts index d76dea115bb..ea8719cbac5 100644 --- a/packages/clerk-js/src/test/mock-helpers.ts +++ b/packages/clerk-js/src/test/mock-helpers.ts @@ -55,6 +55,9 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepVitestMocked { + return ( + typeof value === 'object' && + value !== null && + '__tag' in (value as Record) && + (value as Record).__tag === 'clerk-rq-client' + ); +}; + +const getQueryClientState = (clerk: unknown): { client: QueryClient; isLoaded: boolean } => { + const internal = (clerk as { __internal_queryClient?: ClerkRQClient | undefined }).__internal_queryClient; + + if (isTaggedRQClient(internal)) { + return { client: internal.client, isLoaded: true }; + } + + return { client: mockQueryClient, isLoaded: false }; +}; + const useClerkQueryClient = (): [QueryClient, boolean] => { const clerk = useClerkInstanceContext(); - // @ts-expect-error - __internal_queryClient is not typed - const queryClient = clerk.__internal_queryClient as { __tag: 'clerk-rq-client'; client: QueryClient } | undefined; - const [, setQueryClientLoaded] = useState( - typeof queryClient === 'object' && '__tag' in queryClient && queryClient.__tag === 'clerk-rq-client', - ); + const [state, setState] = useState<{ client: QueryClient; isLoaded: boolean }>(() => getQueryClientState(clerk)); useEffect(() => { - const _setQueryClientLoaded = () => setQueryClientLoaded(true); - // @ts-expect-error - queryClientStatus is not typed - clerk.on('queryClientStatus', _setQueryClientLoaded); - return () => { - // @ts-expect-error - queryClientStatus is not typed - clerk.off('queryClientStatus', _setQueryClientLoaded); + const handleStatusChange = () => { + setState(getQueryClientState(clerk)); }; - }, [clerk, setQueryClientLoaded]); - const isLoaded = typeof queryClient === 'object' && '__tag' in queryClient && queryClient.__tag === 'clerk-rq-client'; + // @ts-expect-error - queryClientStatus is not typed on Clerk + clerk.on('queryClientStatus', handleStatusChange); + + return () => { + // @ts-expect-error - queryClientStatus is not typed on Clerk + clerk.off('queryClientStatus', handleStatusChange); + }; + }, [clerk]); - return [queryClient?.client || mockQueryClient, isLoaded]; + return [state.client, state.isLoaded]; }; export { useClerkQueryClient }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4e64573239..c63ab4d0b00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,10 @@ catalogs: zx: specifier: 8.8.5 version: 8.8.5 + tanstack: + '@tanstack/query-core': + specifier: 5.87.4 + version: 5.87.4 overrides: jest: 29.7.0 @@ -459,7 +463,7 @@ importers: specifier: ^0.5.17 version: 0.5.17 '@tanstack/query-core': - specifier: 5.87.4 + specifier: catalog:tanstack version: 5.87.4 '@zxcvbn-ts/core': specifier: 3.0.4 @@ -939,7 +943,7 @@ importers: specifier: 5.6.0 version: 5.6.0 '@tanstack/query-core': - specifier: 5.87.4 + specifier: catalog:tanstack version: 5.87.4 '@types/glob-to-regexp': specifier: 0.4.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 39b6bb4fc0d..30e7ddaf868 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -18,6 +18,10 @@ catalogs: react: ^18.0.0 || ^19.0.0 || ^19.0.0-0 react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0 + # Can be referenced through "catalog:tanstack" + tanstack: + '@tanstack/query-core': 5.87.4 + # Can be referenced through "catalog:repo" repo: tslib: 2.8.1 From 3bb19da55989bb6e4cf1538f0365586ba9ba1471 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 21 Nov 2025 19:53:33 +0200 Subject: [PATCH 2/5] changeset --- .changeset/thirty-pears-reply.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/thirty-pears-reply.md diff --git a/.changeset/thirty-pears-reply.md b/.changeset/thirty-pears-reply.md new file mode 100644 index 00000000000..783bd8d0c87 --- /dev/null +++ b/.changeset/thirty-pears-reply.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': patch +'@clerk/shared': patch +--- + +Disable retry in queryClient. From 8b3311c768c5be564a685d934fbfb3a68b18a6e8 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 21 Nov 2025 19:29:36 +0200 Subject: [PATCH 3/5] chore(shared,clerk-js): Set defaultOptions to queryClient --- packages/clerk-js/package.json | 2 +- packages/clerk-js/src/core/clerk.ts | 56 ++++++++++++++----- packages/clerk-js/src/test/mock-helpers.ts | 3 + packages/shared/package.json | 2 +- .../react/clerk-rq/use-clerk-query-client.ts | 47 +++++++++++----- pnpm-lock.yaml | 8 ++- pnpm-workspace.yaml | 4 ++ 7 files changed, 89 insertions(+), 33 deletions(-) diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index d9dba6d50c5..d672736feb3 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -70,7 +70,7 @@ "@formkit/auto-animate": "^0.8.2", "@stripe/stripe-js": "5.6.0", "@swc/helpers": "^0.5.17", - "@tanstack/query-core": "5.87.4", + "@tanstack/query-core": "catalog:tanstack", "@zxcvbn-ts/core": "3.0.4", "@zxcvbn-ts/language-common": "3.0.4", "alien-signals": "2.0.6", diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index da8f4f62bb6..e023d6d9fe8 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -97,7 +97,7 @@ import type { } from '@clerk/shared/types'; import { addClerkPrefix, isAbsoluteUrl, stripScheme } from '@clerk/shared/url'; import { allSettled, handleValueOrFn, noop } from '@clerk/shared/utils'; -import type { QueryClient } from '@tanstack/query-core'; +import type { QueryClient, QueryClientConfig } from '@tanstack/query-core'; import { debugLogger, initDebugLogger } from '@/utils/debug'; @@ -201,6 +201,24 @@ const defaultOptions: ClerkOptions = { newSubscriptionRedirectUrl: undefined, }; +const RQ_CLIENT_TAG = 'clerk-rq-client' as const; + +type ClerkRQClient = { __tag: typeof RQ_CLIENT_TAG; client: QueryClient }; + +const clerkQueryClientConfig: QueryClientConfig = { + defaultOptions: { + queries: { + // use the retry logic that fapiClient uses + retry: false, + // Note: to refetch onWindowFocus, you need to call `queryClient.mount()` + refetchOnWindowFocus: false, + refetchOnReconnect: false, + // the query will refetch on mount if the data is stale + refetchOnMount: true, + }, + }, +}; + export class Clerk implements ClerkInterface { public static mountComponentRenderer?: MountComponentRenderer; @@ -246,23 +264,12 @@ export class Clerk implements ClerkInterface { #touchThrottledUntil = 0; #publicEventBus = createClerkEventBus(); - get __internal_queryClient(): { __tag: 'clerk-rq-client'; client: QueryClient } | undefined { - if (!this.#queryClient) { - void import('./query-core') - .then(module => module.QueryClient) - .then(QueryClient => { - if (this.#queryClient) { - return; - } - this.#queryClient = new QueryClient(); - // @ts-expect-error - queryClientStatus is not typed - this.#publicEventBus.emit('queryClientStatus', 'ready'); - }); - } + get __internal_queryClient(): ClerkRQClient | undefined { + this.#initQueryClient(); return this.#queryClient ? { - __tag: 'clerk-rq-client', + __tag: RQ_CLIENT_TAG, client: this.#queryClient, } : undefined; @@ -292,6 +299,25 @@ export class Clerk implements ClerkInterface { public __internal_setActiveInProgress = false; + #initQueryClient = (): void => { + if (this.#queryClient) { + return; + } + + void import('./query-core') + .then(module => module.QueryClient) + .then(QueryClientCtor => { + if (this.#queryClient) { + return; + } + + this.#queryClient = new QueryClientCtor(clerkQueryClientConfig); + + // @ts-expect-error - queryClientStatus is not typed + this.#publicEventBus.emit('queryClientStatus', 'ready'); + }); + }; + get publishableKey(): string { return this.#publishableKey; } diff --git a/packages/clerk-js/src/test/mock-helpers.ts b/packages/clerk-js/src/test/mock-helpers.ts index d76dea115bb..ea8719cbac5 100644 --- a/packages/clerk-js/src/test/mock-helpers.ts +++ b/packages/clerk-js/src/test/mock-helpers.ts @@ -55,6 +55,9 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepVitestMocked { + return ( + typeof value === 'object' && + value !== null && + '__tag' in (value as Record) && + (value as Record).__tag === 'clerk-rq-client' + ); +}; + +const getQueryClientState = (clerk: unknown): { client: QueryClient; isLoaded: boolean } => { + const internal = (clerk as { __internal_queryClient?: ClerkRQClient | undefined }).__internal_queryClient; + + if (isTaggedRQClient(internal)) { + return { client: internal.client, isLoaded: true }; + } + + return { client: mockQueryClient, isLoaded: false }; +}; + const useClerkQueryClient = (): [QueryClient, boolean] => { const clerk = useClerkInstanceContext(); - // @ts-expect-error - __internal_queryClient is not typed - const queryClient = clerk.__internal_queryClient as { __tag: 'clerk-rq-client'; client: QueryClient } | undefined; - const [, setQueryClientLoaded] = useState( - typeof queryClient === 'object' && '__tag' in queryClient && queryClient.__tag === 'clerk-rq-client', - ); + const [state, setState] = useState<{ client: QueryClient; isLoaded: boolean }>(() => getQueryClientState(clerk)); useEffect(() => { - const _setQueryClientLoaded = () => setQueryClientLoaded(true); - // @ts-expect-error - queryClientStatus is not typed - clerk.on('queryClientStatus', _setQueryClientLoaded); - return () => { - // @ts-expect-error - queryClientStatus is not typed - clerk.off('queryClientStatus', _setQueryClientLoaded); + const handleStatusChange = () => { + setState(getQueryClientState(clerk)); }; - }, [clerk, setQueryClientLoaded]); - const isLoaded = typeof queryClient === 'object' && '__tag' in queryClient && queryClient.__tag === 'clerk-rq-client'; + // @ts-expect-error - queryClientStatus is not typed on Clerk + clerk.on('queryClientStatus', handleStatusChange); + + return () => { + // @ts-expect-error - queryClientStatus is not typed on Clerk + clerk.off('queryClientStatus', handleStatusChange); + }; + }, [clerk]); - return [queryClient?.client || mockQueryClient, isLoaded]; + return [state.client, state.isLoaded]; }; export { useClerkQueryClient }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4e64573239..c63ab4d0b00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,6 +28,10 @@ catalogs: zx: specifier: 8.8.5 version: 8.8.5 + tanstack: + '@tanstack/query-core': + specifier: 5.87.4 + version: 5.87.4 overrides: jest: 29.7.0 @@ -459,7 +463,7 @@ importers: specifier: ^0.5.17 version: 0.5.17 '@tanstack/query-core': - specifier: 5.87.4 + specifier: catalog:tanstack version: 5.87.4 '@zxcvbn-ts/core': specifier: 3.0.4 @@ -939,7 +943,7 @@ importers: specifier: 5.6.0 version: 5.6.0 '@tanstack/query-core': - specifier: 5.87.4 + specifier: catalog:tanstack version: 5.87.4 '@types/glob-to-regexp': specifier: 0.4.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 39b6bb4fc0d..30e7ddaf868 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -18,6 +18,10 @@ catalogs: react: ^18.0.0 || ^19.0.0 || ^19.0.0-0 react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0 + # Can be referenced through "catalog:tanstack" + tanstack: + '@tanstack/query-core': 5.87.4 + # Can be referenced through "catalog:repo" repo: tslib: 2.8.1 From 9bd928b10e2e0a219614c0eb0cf28a4f2fcc5107 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 21 Nov 2025 19:53:33 +0200 Subject: [PATCH 4/5] changeset --- .changeset/thirty-pears-reply.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/thirty-pears-reply.md diff --git a/.changeset/thirty-pears-reply.md b/.changeset/thirty-pears-reply.md new file mode 100644 index 00000000000..783bd8d0c87 --- /dev/null +++ b/.changeset/thirty-pears-reply.md @@ -0,0 +1,6 @@ +--- +'@clerk/clerk-js': patch +'@clerk/shared': patch +--- + +Disable retry in queryClient. From bb5028cf22a4891417396aec4a8e992e4ab9c8a4 Mon Sep 17 00:00:00 2001 From: panteliselef Date: Fri, 28 Nov 2025 16:53:10 +0200 Subject: [PATCH 5/5] fix attempt #1 --- .../react/clerk-rq/use-clerk-query-client.ts | 45 ++++++------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/packages/shared/src/react/clerk-rq/use-clerk-query-client.ts b/packages/shared/src/react/clerk-rq/use-clerk-query-client.ts index 444f5887ffb..e6e9d76556d 100644 --- a/packages/shared/src/react/clerk-rq/use-clerk-query-client.ts +++ b/packages/shared/src/react/clerk-rq/use-clerk-query-client.ts @@ -59,45 +59,28 @@ const mockQueryClient = createRecursiveProxy('ClerkMockQueryClient') as unknown type ClerkRQClient = { __tag: 'clerk-rq-client'; client: QueryClient }; -const isTaggedRQClient = (value: unknown): value is ClerkRQClient => { - return ( - typeof value === 'object' && - value !== null && - '__tag' in (value as Record) && - (value as Record).__tag === 'clerk-rq-client' - ); -}; - -const getQueryClientState = (clerk: unknown): { client: QueryClient; isLoaded: boolean } => { - const internal = (clerk as { __internal_queryClient?: ClerkRQClient | undefined }).__internal_queryClient; - - if (isTaggedRQClient(internal)) { - return { client: internal.client, isLoaded: true }; - } - - return { client: mockQueryClient, isLoaded: false }; -}; - const useClerkQueryClient = (): [QueryClient, boolean] => { const clerk = useClerkInstanceContext(); - const [state, setState] = useState<{ client: QueryClient; isLoaded: boolean }>(() => getQueryClientState(clerk)); + // @ts-expect-error - __internal_queryClient is not typed + const queryClient = clerk.__internal_queryClient as ClerkRQClient | undefined; + const [, setQueryClientLoaded] = useState( + typeof queryClient === 'object' && '__tag' in queryClient && queryClient.__tag === 'clerk-rq-client', + ); useEffect(() => { - const handleStatusChange = () => { - setState(getQueryClientState(clerk)); - }; - - // @ts-expect-error - queryClientStatus is not typed on Clerk - clerk.on('queryClientStatus', handleStatusChange); - + const _setQueryClientLoaded = () => setQueryClientLoaded(true); + // @ts-expect-error - queryClientStatus is not typed + clerk.on('queryClientStatus', _setQueryClientLoaded); return () => { - // @ts-expect-error - queryClientStatus is not typed on Clerk - clerk.off('queryClientStatus', handleStatusChange); + // @ts-expect-error - queryClientStatus is not typed + clerk.off('queryClientStatus', _setQueryClientLoaded); }; - }, [clerk]); + }, [clerk, setQueryClientLoaded]); + + const isLoaded = typeof queryClient === 'object' && '__tag' in queryClient && queryClient.__tag === 'clerk-rq-client'; - return [state.client, state.isLoaded]; + return [queryClient?.client || mockQueryClient, isLoaded]; }; export { useClerkQueryClient };