Skip to content

Conversation

@jacekradko
Copy link
Member

@jacekradko jacekradko commented Jan 8, 2026

This reverts commit 78430f8.

Description

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features
    • Added payment method initialization for billing workflows
    • Added Stripe integration support for payment processing
    • Introduced new billing hooks for managing subscriptions, statements, plans, and payment attempts
    • Enhanced data fetching with improved query optimization and caching strategies

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link

changeset-bot bot commented Jan 8, 2026

⚠️ No Changeset found

Latest commit: 6b63c2a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Jan 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jan 8, 2026 6:34pm

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 8, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7565

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@7565

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@7565

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@7565

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@7565

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7565

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@7565

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@7565

@clerk/express

npm i https://pkg.pr.new/@clerk/express@7565

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@7565

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@7565

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@7565

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@7565

@clerk/react

npm i https://pkg.pr.new/@clerk/react@7565

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@7565

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@7565

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@7565

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@7565

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@7565

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@7565

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@7565

commit: 6b63c2a

@jacekradko jacekradko merged commit ea9fa85 into main Jan 8, 2026
40 of 44 checks passed
@jacekradko jacekradko deleted the jacekradko/revert-swr-removal branch January 8, 2026 18:38
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

This PR implements a dual-implementation strategy for data-fetching hooks, enabling runtime or compile-time selection between SWR and React Query implementations. It introduces a __CLERK_USE_RQ__ global flag and a HookAliasPlugin that resolves virtual:data-hooks/* imports to either .rq.tsx (React Query) or .swr.tsx (SWR) files based on environment configuration. Multiple hooks and components now have paired implementations. Tests are updated with conditional assertions to account for differing behavior between implementations. SWR is added as a direct dependency. Primary implementation files are converted to re-export from virtual modules, delegating implementation selection to build-time plugin resolution.

Possibly related PRs

  • #7455: Implements complementary dual implementations of data-fetching hooks with SWR and React Query variants, including the same virtual module resolution strategy and conditional hook switching.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Revert "feat(shared): Remove SWR (#7455)"' clearly describes the main change: reverting a previous commit that removed SWR support.
Docstring Coverage ✅ Passed Docstring coverage is 94.74% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @packages/shared/src/react/billing/useStripeClerkLibs.swr.tsx:
- Around line 20-37: The SWR hook useStripeClerkLibs currently always invokes
the Stripe loader; add the same billing gate used in the React Query version by
importing and calling useBillingHookEnabled() and only enable the useSWR fetch
when billingEnabled is true (e.g., use a null key or conditional fetch function)
so that clerk.__internal_loadStripeJs() is not called when billing is disabled;
update useStripeClerkLibs to return null when billing is disabled and keep
existing SWR options (keepPreviousData, revalidateOnFocus, dedupingInterval)
when enabled.

In @packages/shared/src/react/hooks/__tests__/usePlans.spec.tsx:
- Around line 264-277: The test captures the `calls` array before the
asynchronous `waitFor`, causing a race where `calls` may be empty and make
assertions pass vacuously; move the extraction of `calls` (currently using
`getPlansSpy.mock.calls.map(...)`) to after the appropriate `waitFor` completes
(inside each branch that awaits `waitFor`) so the assertions on `calls` reflect
the actual recorded `getPlansSpy` invocations when `isRQ` (via
globalThis.__CLERK_USE_RQ__) is true or false.

In @packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx:
- Around line 203-208: The pagination checks multiply offsetCount by
pageSizeRef.current again, causing wrong results when initialPage > 1; update
the non-infinite branches in hasNextPage and hasPreviousPage to compare against
offsetCount (not offsetCount * pageSizeRef.current): for hasNextPage use count -
offsetCount > page * pageSizeRef.current, and for hasPreviousPage use (page - 1)
* pageSizeRef.current > offsetCount. Keep the triggerInfinite branches using
infiniteQuery as-is.
🧹 Nitpick comments (1)
packages/shared/src/react/billing/useInitializePaymentMethod.swr.tsx (1)

44-52: Silent error suppression during auto-initialization.

The empty catch block silently swallows errors during the automatic initialization triggered by useEffect. While this may be intentional to prevent unhandled promise rejections on mount, it could make debugging difficult if initialization consistently fails.

Consider logging the error at a debug level or ensuring the error is surfaced through useSWRMutation's error state for consumers to handle.

Optional: Log errors for debugging
   useEffect(() => {
     if (!resource?.id) {
       return;
     }

     trigger().catch(() => {
-      // ignore errors
+      // Errors are available via the hook's error state;
+      // this catch prevents unhandled promise rejection warnings
     });
   }, [resource?.id, trigger]);

Comment on lines +20 to +37
function useStripeClerkLibs(): UseStripeClerkLibsResult {
const clerk = useClerk();

const swr = useSWR(
'clerk-stripe-sdk',
async () => {
const loadStripe = (await clerk.__internal_loadStripeJs()) as LoadStripeFn;
return { loadStripe };
},
{
keepPreviousData: true,
revalidateOnFocus: false,
dedupingInterval: Infinity,
},
);

return swr.data ?? null;
}
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

Behavioral inconsistency: Missing billingEnabled guard.

The React Query implementation (useStripeClerkLibs.rq.tsx) uses useBillingHookEnabled() to conditionally enable the query, but this SWR implementation fetches unconditionally. This means:

  • RQ: Only loads Stripe SDK when billing is enabled
  • SWR: Always attempts to load Stripe SDK

This behavioral difference could cause unexpected network requests or errors when billing is disabled.

Proposed fix to align behavior
 import type { loadStripe } from '@stripe/stripe-js';
 
 import { useSWR } from '../clerk-swr';
 import { useClerk } from '../hooks/useClerk';
+import { useBillingHookEnabled } from './useBillingHookEnabled';
 
 // ... types ...
 
 function useStripeClerkLibs(): UseStripeClerkLibsResult {
   const clerk = useClerk();
+  const billingEnabled = useBillingHookEnabled();
 
   const swr = useSWR(
-    'clerk-stripe-sdk',
+    billingEnabled ? 'clerk-stripe-sdk' : null,
     async () => {
       const loadStripe = (await clerk.__internal_loadStripeJs()) as LoadStripeFn;
       return { loadStripe };
     },
     {
       keepPreviousData: true,
       revalidateOnFocus: false,
       dedupingInterval: Infinity,
     },
   );
 
   return swr.data ?? null;
 }
🤖 Prompt for AI Agents
In @packages/shared/src/react/billing/useStripeClerkLibs.swr.tsx around lines 20
- 37, The SWR hook useStripeClerkLibs currently always invokes the Stripe
loader; add the same billing gate used in the React Query version by importing
and calling useBillingHookEnabled() and only enable the useSWR fetch when
billingEnabled is true (e.g., use a null key or conditional fetch function) so
that clerk.__internal_loadStripeJs() is not called when billing is disabled;
update useStripeClerkLibs to return null when billing is disabled and keep
existing SWR options (keepPreviousData, revalidateOnFocus, dedupingInterval)
when enabled.

Comment on lines +264 to +277
const isRQ = Boolean((globalThis as any).__CLERK_USE_RQ__);
const calls = getPlansSpy.mock.calls.map(call => call[0]?.for);
expect(calls.every(value => value === 'user')).toBe(true);

if (isRQ) {
await waitFor(() => expect(getPlansSpy.mock.calls.length).toBeGreaterThanOrEqual(1));
expect(calls.every(value => value === 'user')).toBe(true);
} else {
await waitFor(() => expect(getPlansSpy.mock.calls.length).toBe(1));
expect(getPlansSpy.mock.calls[0][0]).toEqual(
expect.objectContaining({
for: 'user',
}),
);
}
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 | 🟡 Minor

Potential race condition: calls captured before waitFor completes.

The calls array is populated on line 265 before the waitFor on line 268 executes. In the RQ branch, if getPlansSpy.mock.calls is still empty when line 265 runs, the calls.every(...) check on line 269 will return true vacuously (empty array), potentially masking test failures.

Consider moving the calls extraction inside or after the waitFor:

Suggested fix
     if (isRQ) {
       await waitFor(() => expect(getPlansSpy.mock.calls.length).toBeGreaterThanOrEqual(1));
+      const calls = getPlansSpy.mock.calls.map(call => call[0]?.for);
       expect(calls.every(value => value === 'user')).toBe(true);
     } else {
       await waitFor(() => expect(getPlansSpy.mock.calls.length).toBe(1));
       expect(getPlansSpy.mock.calls[0][0]).toEqual(
         expect.objectContaining({
           for: 'user',
         }),
       );
     }

And move the const calls declaration inside the if block or after both branches complete their respective waitFor.

🤖 Prompt for AI Agents
In @packages/shared/src/react/hooks/__tests__/usePlans.spec.tsx around lines 264
- 277, The test captures the `calls` array before the asynchronous `waitFor`,
causing a race where `calls` may be empty and make assertions pass vacuously;
move the extraction of `calls` (currently using
`getPlansSpy.mock.calls.map(...)`) to after the appropriate `waitFor` completes
(inside each branch that awaits `waitFor`) so the assertions on `calls` reflect
the actual recorded `getPlansSpy` invocations when `isRQ` (via
globalThis.__CLERK_USE_RQ__) is true or false.

Comment on lines +203 to +208
const hasNextPage = triggerInfinite
? Boolean(infiniteQuery.hasNextPage)
: count - offsetCount * pageSizeRef.current > page * pageSizeRef.current;
const hasPreviousPage = triggerInfinite
? Boolean(infiniteQuery.hasPreviousPage)
: (page - 1) * pageSizeRef.current > offsetCount * pageSizeRef.current;
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -i "usepages\|infinite"

Repository: clerk/javascript

Length of output: 693


🏁 Script executed:

cat -n packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx | head -250

Repository: clerk/javascript

Length of output: 11324


🏁 Script executed:

# Find tests for this hook
find . -type f \( -name "*.test.*" -o -name "*.spec.*" \) | xargs grep -l "usePagesOrInfinite" 2>/dev/null

Repository: clerk/javascript

Length of output: 276


🏁 Script executed:

# Search for any tests related to pagination in the shared package
find packages/shared -type f \( -name "*.test.*" -o -name "*.spec.*" \) | head -20

Repository: clerk/javascript

Length of output: 1082


🏁 Script executed:

cat -n packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts

Repository: clerk/javascript

Length of output: 41585


Fix pagination logic for hasNextPage and hasPreviousPage when initialPage > 1.

The calculations on lines 205 and 208 multiply offsetCount by pageSizeRef.current again, but offsetCount is already computed as (initialPageRef.current - 1) * pageSizeRef.current on line 201. This causes incorrect pagination state detection.

Compare with line 202 where pageCount correctly uses (count - offsetCount) without extra multiplication.

Suggested fix
  const hasNextPage = triggerInfinite
    ? Boolean(infiniteQuery.hasNextPage)
-    : count - offsetCount * pageSizeRef.current > page * pageSizeRef.current;
+    : count - offsetCount > page * pageSizeRef.current;
  const hasPreviousPage = triggerInfinite
    ? Boolean(infiniteQuery.hasPreviousPage)
-    : (page - 1) * pageSizeRef.current > offsetCount * pageSizeRef.current;
+    : (page - 1) * pageSizeRef.current > offsetCount;
📝 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 hasNextPage = triggerInfinite
? Boolean(infiniteQuery.hasNextPage)
: count - offsetCount * pageSizeRef.current > page * pageSizeRef.current;
const hasPreviousPage = triggerInfinite
? Boolean(infiniteQuery.hasPreviousPage)
: (page - 1) * pageSizeRef.current > offsetCount * pageSizeRef.current;
const hasNextPage = triggerInfinite
? Boolean(infiniteQuery.hasNextPage)
: count - offsetCount > page * pageSizeRef.current;
const hasPreviousPage = triggerInfinite
? Boolean(infiniteQuery.hasPreviousPage)
: (page - 1) * pageSizeRef.current > offsetCount;
🤖 Prompt for AI Agents
In @packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx around lines 203 -
208, The pagination checks multiply offsetCount by pageSizeRef.current again,
causing wrong results when initialPage > 1; update the non-infinite branches in
hasNextPage and hasPreviousPage to compare against offsetCount (not offsetCount
* pageSizeRef.current): for hasNextPage use count - offsetCount > page *
pageSizeRef.current, and for hasPreviousPage use (page - 1) *
pageSizeRef.current > offsetCount. Keep the triggerInfinite branches using
infiniteQuery as-is.

jacekradko added a commit that referenced this pull request Jan 8, 2026
@coderabbitai coderabbitai bot mentioned this pull request Jan 8, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants