Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/thirty-pears-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': patch
'@clerk/shared': patch
---

Disable retry in queryClient.
2 changes: 1 addition & 1 deletion packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
56 changes: 41 additions & 15 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
},
},
};
Comment on lines +208 to +220
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add JSDoc documentation for the exported configuration.

The clerkQueryClientConfig constant is exported but lacks JSDoc documentation. Per coding guidelines, all public APIs must be documented with JSDoc including description, usage notes, and any important behavioral details.

Apply this diff to add documentation:

+/**
+ * Default configuration for the Clerk QueryClient instance.
+ * 
+ * @remarks
+ * - Retries are disabled to use the retry logic from fapiClient
+ * - Window focus and reconnect refetching are disabled by default
+ * - Mount refetching is enabled to fetch stale data on component mount
+ * 
+ * @internal
+ */
 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,
     },
   },
 };

Based on coding guidelines: All public APIs must be documented with JSDoc.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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,
},
},
};
/**
* Default configuration for the Clerk QueryClient instance.
*
* @remarks
* - Retries are disabled to use the retry logic from fapiClient
* - Window focus and reconnect refetching are disabled by default
* - Mount refetching is enabled to fetch stale data on component mount
*
* @internal
*/
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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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');
});
};
Comment on lines +302 to +319
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for dynamic import failures.

The #initQueryClient method lacks error handling if the dynamic import fails. This could occur due to network issues, build problems, or missing modules, leading to silent failures where features depending on the query client won't work without any diagnostic information.

Apply this diff to add error handling:

   #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');
-      });
+      })
+      .catch(err => {
+        console.error('Clerk: Failed to initialize QueryClient', err);
+        // @ts-expect-error - queryClientStatus is not typed
+        this.#publicEventBus.emit('queryClientStatus', 'error');
+      });
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#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');
});
};
#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');
})
.catch(err => {
console.error('Clerk: Failed to initialize QueryClient', err);
// @ts-expect-error - queryClientStatus is not typed
this.#publicEventBus.emit('queryClientStatus', 'error');
});
};
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/clerk.ts around lines 302 to 319, the dynamic
import promise in #initQueryClient has no error handling so import failures are
silent; add a .catch handler to the import chain that logs the error (e.g.,
console.error or an existing logger) and emits a failure status on the public
event bus (e.g., this.#publicEventBus.emit('queryClientStatus', 'error', error)
or similar) so consumers can react, and ensure the handler prevents further
initialization when an error occurs.


get publishableKey(): string {
return this.#publishableKey;
}
Expand Down
3 changes: 3 additions & 0 deletions packages/clerk-js/src/test/mock-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export const mockClerkMethods = (clerk: LoadedClerk): DeepVitestMocked<LoadedCle
// Setting staleTime to Infinity will not cause issues between tests as long as each test
// case has its own wrapper that initializes a Clerk instance with a new QueryClient.
staleTime: Infinity,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
},
},
}),
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
"devDependencies": {
"@stripe/react-stripe-js": "3.1.1",
"@stripe/stripe-js": "5.6.0",
"@tanstack/query-core": "5.87.4",
"@tanstack/query-core": "catalog:tanstack",
"@types/glob-to-regexp": "0.4.4",
"@types/js-cookie": "3.0.6",
"cross-fetch": "^4.1.0",
Expand Down
4 changes: 3 additions & 1 deletion packages/shared/src/react/clerk-rq/use-clerk-query-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,13 @@ function createRecursiveProxy(label: string): RecursiveMock {

const mockQueryClient = createRecursiveProxy('ClerkMockQueryClient') as unknown as QueryClient;

type ClerkRQClient = { __tag: 'clerk-rq-client'; client: QueryClient };

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 queryClient = clerk.__internal_queryClient as ClerkRQClient | undefined;
const [, setQueryClientLoaded] = useState(
typeof queryClient === 'object' && '__tag' in queryClient && queryClient.__tag === 'clerk-rq-client',
);
Expand Down
8 changes: 6 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading