Skip to content

Conversation

@panteliselef
Copy link
Member

@panteliselef panteliselef commented Nov 14, 2025

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

  • Refactor
    • Unified pagination to accept a single params object and standardized cache-key handling across hooks.
  • New Features
    • Added a reusable cache-key utility and a subscription helper to standardize caching and invalidation.
  • Breaking Changes / API
    • Public pagination API now expects a single params object; an experimental cache-mode option was removed from public types.
  • Tests
    • Added reusable test helpers and refactored pagination tests.
  • Chore
    • Added a changeset, adjusted bundle size threshold, and introduced a build-time define symbol.

@changeset-bot
Copy link

changeset-bot bot commented Nov 14, 2025

🦋 Changeset detected

Latest commit: 6bc27c5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 22 packages
Name Type
@clerk/shared Patch
@clerk/agent-toolkit Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js Patch
@clerk/elements Patch
@clerk/expo-passkeys Patch
@clerk/clerk-expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/clerk-react Patch
@clerk/remix Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/themes Patch
@clerk/types Patch
@clerk/vue Patch
@clerk/localizations Patch

Not sure what this means? Click here to learn what changesets are.

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

@vercel
Copy link

vercel bot commented Nov 14, 2025

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

Project Deployment Preview Comments Updated (UTC)
clerk-js-sandbox Ready Ready Preview Comment Nov 17, 2025 11:34am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 14, 2025

Walkthrough

Hook pagination APIs were refactored to accept a single params object ({ fetcher, config, keys }); a generic createCacheKeys utility and subscriptionQuery helper were added; many hooks and tests were migrated to the new key-generation and param shape; a local experimental flag was added in one hook and removed from public types.

Changes

Cohort / File(s) Summary
Core type & signature
packages/shared/src/react/hooks/usePageOrInfinite.types.ts
Updated UsePagesOrInfiniteSignature to accept one object param { fetcher, config, keys }; added internal types (Config, Register, AnyQueryKey, QueryArgs, QueryKeyWithArgs) to model the new query-key shapes.
Implementations: RQ / SWR
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx, packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx
Migrated to object-param form; read pagination options from config; derive keys from keys.queryKey; updated key construction, fetcher invocation, infinite handling, sign-out invalidation, and placeholder/keepPreviousData handling.
Cache key utility
packages/shared/src/react/hooks/createCacheKeys.ts
Added exported generic createCacheKeys returning { queryKey, invalidationKey, stableKey, authenticated } built from stablePrefix, authenticated, tracked, and untracked.
Subscription helper & usage
packages/shared/src/react/hooks/useSubscription.rq.tsx
Added exported subscriptionQuery helper that returns queryKey/invalidationKey/stableKey; refactored useSubscription to use it and changed invalidation to use the invalidationKey.
Dependent hooks — migrated to new API
packages/shared/src/react/hooks/useAPIKeys.ts, packages/shared/src/react/hooks/useOrganization.tsx, packages/shared/src/react/hooks/useOrganizationList.tsx, packages/shared/src/react/hooks/createBillingPaginatedHook.tsx
Replaced positional usePagesOrInfinite calls with object-form { fetcher, config, keys } using createCacheKeys; added isSignedIn, initialPage, pageSize to configs where applicable; createBillingPaginatedHook.tsx adds a local HookParams.__experimental_mode?: 'cache'.
Tests
packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts
Added test scaffolding helpers (createCacheKeys import, ConfigOverrides, buildConfig, buildKeys, renderUsePagesOrInfinite); refactored tests to use helpers and object-form rendering; adjusted wording.
Public types cleanup
packages/shared/src/react/types.ts
Removed public __experimental_mode?: 'cache' from PagesOrInfiniteOptions.
Build/config / changeset / misc
packages/shared/tsdown.config.mts, .changeset/lemon-facts-stare.md, packages/clerk-js/bundlewatch.config.json
Added define symbol __CLERK_USE_RQ__ to tsdown config; added changeset describing cache-key/pagination update; bumped one bundlewatch maxSize threshold slightly.

Sequence Diagram(s)

sequenceDiagram
    participant Consumer as Hook Consumer
    participant Hook as usePagesOrInfinite
    participant Keys as createCacheKeys / keys
    participant Query as Query Layer (RQ/SWR)
    participant Fetcher as Fetcher

    rect `#E8F5E9`
    Note over Consumer,Hook: Single-object call shape
    Consumer->>Hook: call({ fetcher, config, keys })
    Hook->>Keys: read keys.queryKey (stablePrefix, authenticated, tracked, args)
    Hook->>Query: build query key from keys.queryKey (append "-inf" if config.infinite)
    Query->>Fetcher: invoke(fetcher(args + pagination from config))
    Fetcher-->>Query: return paged data
    Query-->>Hook: return PaginatedResources
    Hook-->>Consumer: return resources/state
    end

    rect `#FFF3E0`
    Note over Hook,Query: Sign-out invalidation uses auth flag in key
    Hook->>Query: invalidate queries where key[1] == true and key[0] matches stablePrefix (or "-inf")
    Query-->>Hook: invalidated
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay special attention to type changes in usePageOrInfinite.types.ts for inference and compatibility.
  • Verify consistent createCacheKeys usage (stablePrefix, tracked, untracked.args shape) across migrated hooks.
  • Inspect fetcher argument extraction from keys.queryKey[3].args and places with TypeScript narrows/ignores.
  • Reconcile the local __experimental_mode in createBillingPaginatedHook.tsx with its removal from public types.ts.

Possibly related PRs

Suggested labels

react

Suggested reviewers

  • nikosdouvlis

Poem

"I hop with keys in tidy stacks,
object shapes and fewer hacks,
fetchers hum and caches align,
pages flow smooth like carrot wine,
I nibble tests and mark the tracks. — 🐇"

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore(shared): Improve cache key creation' accurately reflects the main changes across the pull request, which primarily refactor how cache keys are created and managed in the shared hooks library through a new createCacheKeys utility.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch elef/improve-cache-key-construction

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

# Conflicts:
#	packages/shared/src/react/hooks/createBillingPaginatedHook.tsx
@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 14, 2025

Open in StackBlitz

@clerk/agent-toolkit

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

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/elements

npm i https://pkg.pr.new/@clerk/elements@7217

@clerk/clerk-expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/clerk-react

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

@clerk/react-router

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

@clerk/remix

npm i https://pkg.pr.new/@clerk/remix@7217

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/themes

npm i https://pkg.pr.new/@clerk/themes@7217

@clerk/types

npm i https://pkg.pr.new/@clerk/types@7217

@clerk/upgrade

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

@clerk/vue

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

commit: 6bc27c5

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: 1

🧹 Nitpick comments (3)
packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx (1)

88-106: Consider improving type safety around fetcher parameter narrowing.

The @ts-ignore comments on lines 95-96 bypass type checking when calling the fetcher with the result of getDifferentKeys. While the runtime logic appears correct (stripping tracked keys before passing to fetcher), this creates a type safety gap.

Consider one of these approaches:

  1. Use a type assertion with explanation instead of @ts-ignore
  2. Refactor getDifferentKeys to return a type that TypeScript can verify as compatible with Params
  3. Add a generic constraint to ensure the transformation is type-safe

Example with type assertion:

-          // @ts-ignore - fetcher expects Params subset; narrowing at call-site
-          return fetcher(requestParams);
+          return fetcher(requestParams as Params);
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (1)

68-78: Remove outdated comment.

The comment on lines 75-76 references getDifferentKeys and questions the need for parameter transformation, but the code now directly uses args from the query key. This comment is outdated and should be removed.

       if (!fetcher) {
         return undefined as any;
       }

-      // Why do we need this ? can we just specify which `args` to use in the key ?
-      // const requestParams = getDifferentKeys(key, cacheKeys);
       return fetcher(args);
packages/shared/src/react/hooks/usePageOrInfinite.types.ts (1)

7-39: Remove commented-out code blocks.

The commented-out code from the previous API signature can be removed. Git history preserves the old implementation if needed for reference. Keeping large blocks of commented code can confuse future developers and creates maintenance burden.

-// export type UsePagesOrInfiniteSignature = <
-//   Params extends PagesOrInfiniteOptions,
-//   ...
-// ) => PaginatedResources<ExtractData<FetcherReturnData>, TConfig['infinite']>;
-
 type Config = PagesOrInfiniteConfig & PagesOrInfiniteOptions;

And:

 export type UsePagesOrInfiniteSignature = <
   Params,
   FetcherReturnData extends Record<string, any>,
   TCacheKeys extends {
     queryKey: QueryKeyWithArgs<Params>;
     invalidationKey: AnyQueryKey;
     stableKey: string;
   },
-  // CacheKeys extends Record<string, unknown> = Record<string, unknown>,
   TConfig extends Config = Config,
 >(
-  // /**
-  //  * The parameters will be passed to the fetcher.
-  //  */
-  // params: Params,
-  ...
   params: {
     fetcher: ((p: Params) => FetcherReturnData | Promise<FetcherReturnData>) | undefined;
     config: TConfig;
     keys: TCacheKeys;
   },
 ) => PaginatedResources<ExtractData<FetcherReturnData>, TConfig['infinite']>;

Also applies to: 82-95

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9f48f13 and 9e2f4e7.

📒 Files selected for processing (11)
  • packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts (25 hunks)
  • packages/shared/src/react/hooks/createBillingPaginatedHook.tsx (3 hunks)
  • packages/shared/src/react/hooks/createCacheKeys.ts (1 hunks)
  • packages/shared/src/react/hooks/useAPIKeys.ts (2 hunks)
  • packages/shared/src/react/hooks/useOrganization.tsx (2 hunks)
  • packages/shared/src/react/hooks/useOrganizationList.tsx (2 hunks)
  • packages/shared/src/react/hooks/usePageOrInfinite.types.ts (1 hunks)
  • packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (6 hunks)
  • packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx (4 hunks)
  • packages/shared/src/react/hooks/useSubscription.rq.tsx (3 hunks)
  • packages/shared/src/react/types.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • packages/shared/src/react/types.ts
🧰 Additional context used
🧬 Code graph analysis (8)
packages/shared/src/react/hooks/useAPIKeys.ts (4)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (1)
  • usePagesOrInfinite (21-294)
packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx (1)
  • usePagesOrInfinite (37-237)
packages/shared/src/types/clerk.ts (1)
  • GetAPIKeysParams (1980-1983)
packages/shared/src/react/hooks/createCacheKeys.ts (1)
  • createCacheKeys (4-23)
packages/shared/src/react/hooks/createBillingPaginatedHook.tsx (2)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (1)
  • usePagesOrInfinite (21-294)
packages/shared/src/react/hooks/createCacheKeys.ts (1)
  • createCacheKeys (4-23)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (4)
packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx (1)
  • usePagesOrInfinite (37-237)
packages/shared/src/react/hooks/usePageOrInfinite.types.ts (1)
  • UsePagesOrInfiniteSignature (71-101)
packages/shared/src/react/clerk-rq/useQuery.ts (1)
  • useClerkQuery (40-42)
packages/shared/src/react/clerk-rq/useInfiniteQuery.ts (1)
  • useClerkInfiniteQuery (42-44)
packages/shared/src/react/hooks/useOrganization.tsx (2)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (1)
  • usePagesOrInfinite (21-294)
packages/shared/src/react/hooks/createCacheKeys.ts (1)
  • createCacheKeys (4-23)
packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts (2)
packages/shared/src/react/hooks/createCacheKeys.ts (1)
  • createCacheKeys (4-23)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (1)
  • usePagesOrInfinite (21-294)
packages/shared/src/react/hooks/usePageOrInfinite.types.ts (1)
packages/shared/src/react/types.ts (2)
  • PagesOrInfiniteConfig (104-128)
  • PagesOrInfiniteOptions (133-146)
packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx (3)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (1)
  • usePagesOrInfinite (21-294)
packages/shared/src/react/hooks/usePageOrInfinite.types.ts (1)
  • UsePagesOrInfiniteSignature (71-101)
packages/shared/src/react/hooks/usePagesOrInfinite.shared.ts (1)
  • getDifferentKeys (75-89)
packages/shared/src/react/hooks/useOrganizationList.tsx (3)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (1)
  • usePagesOrInfinite (21-294)
packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx (1)
  • usePagesOrInfinite (37-237)
packages/shared/src/react/hooks/createCacheKeys.ts (1)
  • createCacheKeys (4-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (13)
packages/shared/src/react/hooks/useSubscription.rq.tsx (3)

24-35: LGTM: Clean separation of query and invalidation keys.

The subscriptionQuery helper effectively separates queryKey from invalidationKey, enabling broader cache invalidation (invalidating all queries with matching trackedKeys regardless of untrackedKeys). The hardcoded 'commerce-subscription' stableKey is appropriate given the function's internal, subscription-specific purpose.

Note: When untrackedKeys is omitted, the queryKey will be [stableKey, trackedKeys, undefined], which React Query treats distinctly from a two-element array. This appears intentional to reserve space for future untracked parameters.


63-70: LGTM: Correct usage of the new helper.

The refactor properly leverages subscriptionQuery to generate keys, and the useMemo dependencies correctly track all values used in trackedKeys. The absence of untrackedKeys is appropriate for the current use case and can be added later if needed.


85-88: LGTM: Improved invalidation strategy.

Switching from queryKey to invalidationKey is the core improvement—it enables broader cache invalidation by matching on [stableKey, trackedKeys] only, ensuring all related queries are refreshed regardless of their untrackedKeys. This is a more flexible and maintainable approach.

packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts (2)

16-53: LGTM! Well-structured test helpers.

The test scaffolding helpers effectively reduce boilerplate and standardize the test setup for the new object-based API. The buildConfig and buildKeys abstractions are clean and the renderUsePagesOrInfinite wrapper provides a consistent rendering interface.


71-860: Test migration looks comprehensive.

All test cases have been consistently updated to use the new object-based API through the helper functions. The test coverage remains thorough, covering pagination, infinite mode, caching, error handling, and edge cases.

packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx (2)

37-57: Signature refactor correctly implemented.

The update to accept a single params object and source initialPage/pageSize from config instead of params is consistent with the PR's goal of standardizing the API. Cache key construction correctly destructures the keys.queryKey array.


124-153: Infinite mode refactor aligns with pagination mode.

The infinite mode updates correctly mirror the pagination mode changes, using keys.queryKey elements for cache key construction. The same type safety consideration from the previous comment applies to lines 147-150.

packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (4)

11-11: Good simplification by removing getDifferentKeys.

The React Query implementation no longer needs getDifferentKeys since it can access args directly from the query key structure. This is cleaner than the transformation approach.


21-28: Signature refactor correctly implemented.

The update to accept a single params object and destructure fetcher, config, and keys is consistent with the SWR implementation and the broader PR refactor.


115-136: Verify the scope of the sign-out query clear predicate.

The predicate on line 126 clears all queries where key[2] === true (authenticated queries). This is broader than the previous implementation which matched specific query key patterns. If other authenticated queries exist in the application (e.g., user profile, organization data), they will also be cleared on sign-out.

Please verify this is the intended behavior. If the goal is to clear only pagination-related queries on sign-out, consider a more specific predicate:

queryClient.removeQueries({
  predicate: query => {
    const key = query.queryKey;
    // Clear only pagination queries that are authenticated
    return Array.isArray(key) && 
           key[2] === true && 
           (typeof key[0] === 'string' && key[0].includes('your-pagination-prefix'));
  },
});

Alternatively, if clearing all authenticated queries on sign-out is intentional, consider adding a comment explaining this behavior.


86-109: Infinite query refactor correctly implemented.

The infinite query updates are consistent with the single-page query changes, using keys.queryKey elements for construction and directly accessing args from the query key in the queryFn.

packages/shared/src/react/hooks/usePageOrInfinite.types.ts (2)

40-69: New type definitions support the refactored signature.

The internal type definitions (Config, Register, AnyQueryKey, QueryArgs, QueryKeyWithArgs) correctly support the new object-based API. The QueryKeyWithArgs type properly defines the expected structure of cache keys as [string, boolean, Record<string, unknown>, QueryArgs<Params>, ...Array<unknown>], which aligns with the output of createCacheKeys.


71-101: Signature successfully refactored to object-based API.

The updated UsePagesOrInfiniteSignature correctly defines the new single-parameter API where params contains fetcher, config, and keys. The generic constraints properly enforce that TCacheKeys includes queryKey, invalidationKey, and stableKey with the expected shapes.

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: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9e2f4e7 and 2f08c8b.

📒 Files selected for processing (1)
  • packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts (25 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts (1)
packages/shared/src/react/hooks/createCacheKeys.ts (1)
  • createCacheKeys (4-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (3)
packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts (3)

5-5: LGTM! Clean test helper utilities.

The new test helpers (ConfigOverrides, buildConfig, buildKeys, renderUsePagesOrInfinite) effectively reduce duplication and align with the refactored object-based API. The use of nullish coalescing in buildConfig correctly prioritizes overrides, then params, then defaults.

Also applies to: 16-53


71-71: LGTM! Comment accurately reflects query client usage.

The updated wording correctly references the query client, aligning with the refactored implementation.


80-84: LGTM! Consistent test refactoring.

The tests have been successfully refactored to use the new helper utilities while preserving all original assertions. The object-based API ({ fetcher, config, keys }) is applied consistently across all test cases.

Also applies to: 132-136, 174-178, 229-233, 275-279, 310-314, 343-347, 376-380, 406-410, 441-445, 482-486, 520-524, 563-567, 606-610, 663-667, 677-681, 755-759, 788-792, 819-823

Comment on lines +245 to +247
const params = { initialPage: 1, pageSize: 3 };
const config = buildConfig(params, { isSignedIn: false });
const keys = buildKeys('t-signedin-false', params);
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

Consider aligning authenticated flag with isSignedIn config.

In these tests, the config specifies isSignedIn: false but buildKeys defaults authenticated to true. While this doesn't break the tests (since the query is disabled), it creates a semantic mismatch—the cache keys should reflect the authentication state for consistency and clarity.

Apply this diff to align the authenticated flag:

   const params = { initialPage: 1, pageSize: 3 };
   const config = buildConfig(params, { isSignedIn: false });
-  const keys = buildKeys('t-signedin-false', params);
+  const keys = buildKeys('t-signedin-false', params, {}, false);

And similarly for line 261:

   const params = { initialPage: 1, pageSize: 3 };
   const config = buildConfig(params, { infinite: true, isSignedIn: false });
-  const keys = buildKeys('t-signedin-false-inf', params);
+  const keys = buildKeys('t-signedin-false-inf', params, {}, false);

Also applies to: 259-261

🤖 Prompt for AI Agents
In packages/shared/src/react/hooks/__tests__/usePagesOrInfinite.spec.ts around
lines 245-247 (and similarly 259-261), the test builds a config with isSignedIn:
false but calls buildKeys which defaults authenticated to true, causing a
semantic mismatch; update the buildKeys calls in these line ranges to pass
authenticated: false (or derive authenticated from the config) so the generated
cache keys reflect the authentication state consistently with isSignedIn.

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: 1

🧹 Nitpick comments (2)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (2)

75-77: Remove or address the TODO comment.

The comment on lines 75-76 contains an unresolved question about the implementation. Either remove it if the current approach is confirmed correct, or open an issue to track the investigation.


86-90: Consider using a more structured approach for the infinite key suffix.

The string concatenation stablePrefix + '-inf' works but assumes stablePrefix is a string and doesn't prevent duplicate suffixes. Consider whether the key structure should enforce this at the type level or use a more explicit infinite flag in the key tuple.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2f08c8b and f981de3.

📒 Files selected for processing (1)
  • packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (4)
packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx (1)
  • usePagesOrInfinite (37-237)
packages/shared/src/react/hooks/usePageOrInfinite.types.ts (1)
  • UsePagesOrInfiniteSignature (71-101)
packages/shared/src/react/clerk-rq/useQuery.ts (1)
  • useClerkQuery (40-42)
packages/shared/src/react/clerk-rq/useInfiniteQuery.ts (1)
  • useClerkInfiniteQuery (42-44)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (5)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (5)

21-22: LGTM: Clean refactor to object-based API.

The signature change from positional arguments to a single params object improves clarity and aligns with the type definition in usePageOrInfinite.types.ts.


24-28: LGTM: Correct config usage.

Properly sources initialPage and pageSize from the config object consistent with the new API structure.


92-109: LGTM: Consistent infinite query implementation.

Properly uses config values for pagination parameters and correctly spreads args from the queryKey when calling the fetcher.


220-220: LGTM: Simplified error handling.

Removing the type cast simplifies the code while maintaining correctness.


48-64: No type safety concern—destructuring is properly enforced by types.

The QueryKeyWithArgs<Params> type explicitly defines the first four elements as a readonly tuple with specific types: readonly [string, boolean, Record<string, unknown>, QueryArgs<Params>, ...Array<unknown>]. TypeScript enforces that these four positions exist and have the specified types, making the destructuring const [stablePrefix, authenticated, tracked, untracked] = keys.queryKey type-safe at compile time. No runtime checks are needed.

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: 0

🧹 Nitpick comments (1)
packages/shared/src/react/hooks/createBillingPaginatedHook.tsx (1)

39-48: Experimental __experimental_mode flag is wired consistently with behavior

The new __experimental_mode?: 'cache' option is documented to avoid triggering a request on mount and to read from cache, and it is correctly passed through to usePagesOrInfinite where config.__experimental_mode === 'cache' disables querying and keeps cache-only semantics. From this file’s perspective, the type and docs look consistent with the underlying behavior.

Minor nit: if you touch this again, consider rephrasing the comment to “In cache mode …” for slightly clearer wording, but this is non-blocking.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f981de3 and 3b82d78.

📒 Files selected for processing (2)
  • .changeset/lemon-facts-stare.md (1 hunks)
  • packages/shared/src/react/hooks/createBillingPaginatedHook.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/shared/src/react/hooks/createBillingPaginatedHook.tsx (2)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (1)
  • usePagesOrInfinite (21-294)
packages/shared/src/react/hooks/createCacheKeys.ts (1)
  • createCacheKeys (4-23)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (2)
.changeset/lemon-facts-stare.md (1)

1-5: Changeset accurately reflects the scoped change

The changeset targets @clerk/shared with a patch and concisely describes the cache key updates for SWR/RQ hooks; this aligns with the implementation changes in the PR.

packages/shared/src/react/hooks/createBillingPaginatedHook.tsx (1)

10-10: Cache key construction via createCacheKeys and new usePagesOrInfinite call look correct; confirm args shape

The new integration with usePagesOrInfinite and createCacheKeys looks coherent:

  • fetcher: fetchFn matches the expected signature, and enabled: isEnabled keeps the previous gating (clerk loaded, billing enabled, external enabled flag).
  • config.isSignedIn is only set when options?.unauthenticated is falsy, which aligns with the use of the authenticated dimension for sign‑out‑driven invalidation.
  • createCacheKeys is fed:
    • stablePrefix: resourceType to namespace billing resources,
    • authenticated: !options?.unauthenticated so billing queries are treated as authenticated by default and get cleared on sign‑out,
    • tracked that differentiates:
      • unauthenticated usage by { for: safeFor },
      • authenticated usage by { userId, orgId/_orgId? }, with the computed org key respecting the __CLERK_USE_RQ__ build flag,
    • untracked: { args: hookParams as TParams } so the full argument payload participates in the key but is not part of the invalidation key.

This matches the usePagesOrInfinite expectations in the shared hook layer and should keep cache invalidation and scoping behavior consistent.

One thing worth double‑checking (outside this file) is that hookParams indeed contains all request‑relevant parameters expected by the underlying useFetcher implementation, not just initialPage, pageSize, and optional orgId. If additional filters were ever added to billing hooks, they should be included in hookParams so they contribute both to the fetcher arguments and to the cache key.

Also applies to: 124-148

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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (1)

237-244: Bug in hasNextPage / hasPreviousPage when initialPage > 1

offsetCount is already in “items” units: (initialPageRef.current - 1) * pageSizeRef.current. The current checks multiply it by pageSizeRef.current again:

count - offsetCount * pageSizeRef.current > page * pageSizeRef.current;
(page - 1) * pageSizeRef.current > offsetCount * pageSizeRef.current;

This double-multiplication makes hasNextPage and hasPreviousPage incorrect whenever initialPageRef.current is not 1 (and generally breaks dimensional consistency).

You can fix this by comparing against offsetCount directly:

   const offsetCount = (initialPageRef.current - 1) * pageSizeRef.current;
   const pageCount = Math.ceil((count - offsetCount) / pageSizeRef.current);
   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;

This preserves existing semantics for initialPage = 1 and fixes the logic for non‑default initialPage.

🧹 Nitpick comments (2)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (2)

11-12: Optional: simplify useWithSafeValues re-export

useWithSafeValues is only used for re-export. You can reduce boilerplate by exporting it directly from the import source:

-import { useWithSafeValues } from './usePagesOrInfinite.shared';
-
-export { useWithSafeValues };
+export { useWithSafeValues } from './usePagesOrInfinite.shared';

This avoids an extra import binding and keeps the module boundary clearer.

Also applies to: 294-294


112-134: Sign‑out cache purge is now properly scoped to this hook

The updated removeQueries predicate that destructures [stablePrefix, authenticated] from query.queryKey and checks:

  • authenticated === true, and
  • stablePrefix equals either keys.queryKey[0] or keys.queryKey[0] + '-inf'

ensures only this hook’s authenticated pagination queries (both single and infinite) are cleared on sign-out. This addresses the previous over-broad invalidation and keeps unrelated authenticated queries intact.

If you want additional defensive safety against unexpected key shapes, you could optionally add an Array.isArray(query.queryKey) guard before destructuring, but it’s not strictly necessary given the current usage.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3b82d78 and 0c9f8cf.

📒 Files selected for processing (3)
  • packages/shared/src/react/hooks/createCacheKeys.ts (1 hunks)
  • packages/shared/src/react/hooks/usePageOrInfinite.types.ts (1 hunks)
  • packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/shared/src/react/hooks/createCacheKeys.ts
🧰 Additional context used
🧬 Code graph analysis (2)
packages/shared/src/react/hooks/usePageOrInfinite.types.ts (1)
packages/shared/src/react/types.ts (3)
  • PagesOrInfiniteConfig (104-128)
  • PagesOrInfiniteOptions (133-146)
  • PaginatedResources (13-79)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (2)
packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx (1)
  • usePagesOrInfinite (37-237)
packages/shared/src/react/hooks/usePageOrInfinite.types.ts (1)
  • UsePagesOrInfiniteSignature (38-51)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Build Packages
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (7)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (4)

21-35: Params-object refactor and config defaults look consistent

The move to a single params object with { fetcher, config, keys } plus deriving paginatedPage, initialPageRef, and pageSizeRef from config is internally consistent and matches the shared signature. Using enabled, __experimental_mode === 'cache', and isSignedIn !== false together for queriesEnabled is a reasonable gate for network work.


48-64: Cache key construction and single-page fetcher correctly align with QueryKeyWithArgs

The pagesQueryKey derivation

  • Unpacks [stablePrefix, authenticated, tracked, untracked] from keys.queryKey.
  • Rebuilds the fourth element with an updated args object that injects initialPage and pageSize while preserving any existing params.
  • Uses queryKey[3].args in queryFn to call fetcher(args).

This matches the tuple layout enforced by QueryKeyWithArgs and cleanly ties the current page/pageSize into the key without extra getDifferentKeys indirection.

Also applies to: 69-76


84-107: Infinite query key and paging logic are coherent with the new key shape

Using an infiniteQueryKey of [stablePrefix + '-inf', authenticated, tracked, untracked] cleanly separates infinite-mode cache entries from single-page ones while reusing the same base metadata. The initialPageParam, consumed computation, and queryFn that spreads args and overrides initialPage/pageSize with { pageParam, pageSizeRef.current } are consistent with the intended pagination behavior and the shared args structure.


216-220: Error normalization is straightforward

Normalizing to const error = (triggerInfinite ? infiniteQuery.error : singlePageQuery.error) ?? null; simplifies downstream checks and avoids casts while keeping the external contract (ClerkAPIResponseError | null) intact.

packages/shared/src/react/hooks/usePageOrInfinite.types.ts (3)

7-25: Config alias and AnyQueryKey scaffolding align with usage

Defining

type Config = PagesOrInfiniteConfig & PagesOrInfiniteOptions;

and deriving AnyQueryKey from a mergeable Register interface gives you a single internal config shape and a flexible invalidation key type. When Register.queryKey is present, AnyQueryKey respects its array shape; otherwise it safely falls back to ReadonlyArray<unknown>. This matches how invalidationKey is used in the updated signature.


26-36: QueryKeyWithArgs tuple matches the runtime key layout

The QueryArgs<Params> wrapper and

type QueryKeyWithArgs<Params> = readonly [
  string,
  boolean,
  Record<string, unknown>,
  QueryArgs<Params>,
  ...Array<unknown>,
];

precisely describe the [stablePrefix, authenticated, tracked, { args: Params }, …] structure consumed in usePagesOrInfinite.rq.tsx. This keeps query key construction and consumption type-safe while leaving room for additional suffix segments if needed.


38-51: Updated UsePagesOrInfiniteSignature keeps the public API typed and coherent

The new signature:

  • Switches to a single params object with { fetcher, config, keys }.
  • Ties keys.queryKey to QueryKeyWithArgs<Params> and invalidationKey to AnyQueryKey.
  • Uses TConfig extends Config so that TConfig['infinite'] cleanly drives the PaginatedResources<…, Infinite> generic.

This matches the refactored hook implementation and should give consumers better inference around both pagination mode and return types.

# Conflicts:
#	packages/shared/src/react/hooks/useAPIKeys.ts
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: 0

🧹 Nitpick comments (1)
packages/shared/src/react/hooks/useAPIKeys.ts (1)

87-105: Consider clarifying the duplication of initialPage and pageSize.

Both initialPage and pageSize appear in two places:

  • In hookParams (lines 88-89), which becomes untracked.args
  • In config (lines 103-104)

Based on the usePagesOrInfinite implementation, the values in untracked.args are overridden when constructing query keys—the hook uses config.initialPage and config.pageSize for internal pagination state and replaces the values in args before passing them to the fetcher.

This duplication may be intentional to ensure initial alignment, but it could be clarified with a comment or refactored to avoid redundancy if the values in hookParams serve no purpose.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 38da12d and 6bc27c5.

📒 Files selected for processing (1)
  • packages/shared/src/react/hooks/useAPIKeys.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/shared/src/react/hooks/useAPIKeys.ts (2)
packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx (1)
  • usePagesOrInfinite (21-292)
packages/shared/src/react/hooks/createCacheKeys.ts (1)
  • createCacheKeys (4-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build Packages
  • GitHub Check: Formatting | Dedupe | Changeset
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (2)
packages/shared/src/react/hooks/useAPIKeys.ts (2)

4-4: LGTM! Imports support the new cache key architecture.

The addition of GetAPIKeysParams and createCacheKeys properly supports the refactored cache key generation pattern.

Also applies to: 7-7


106-116: LGTM! Cache key structure is well-designed.

The createCacheKeys usage properly separates concerns:

  • tracked: { subject } affects invalidation, appropriate for stable identifiers
  • untracked: { args: hookParams } includes transient parameters like query
  • authenticated consistently matches isSignedIn (both using Boolean(clerk.user))

This structure ensures efficient cache invalidation while supporting query-specific cache entries.

@panteliselef panteliselef merged commit 1d4e7a7 into main Nov 17, 2025
45 checks passed
@panteliselef panteliselef deleted the elef/improve-cache-key-construction branch November 17, 2025 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants