Skip to content

Commit 73e9101

Browse files
committed
fix conflicts about usePagesOrInfinite.swr
1 parent eeae1c5 commit 73e9101

File tree

7 files changed

+119
-352
lines changed

7 files changed

+119
-352
lines changed

packages/react/src/contexts/ClerkContextProvider.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,7 @@ export function ClerkContextProvider(props: ClerkContextProvider) {
9393
<IsomorphicClerkContext.Provider value={clerkCtx}>
9494
<ClientContext.Provider value={clientCtx}>
9595
<SessionContext.Provider value={sessionCtx}>
96-
<OrganizationProvider
97-
// key={clerkStatus + queryStatus}
98-
{...organizationCtx.value}
99-
>
96+
<OrganizationProvider {...organizationCtx.value}>
10097
<AuthContext.Provider value={authCtx}>
10198
<UserContext.Provider value={userCtx}>
10299
<CheckoutProvider

packages/shared/src/react/hooks/usePageOrInfinite.types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,17 @@ export type UsePagesOrInfiniteSignature = <
1010
CacheKeys extends Record<string, unknown> = Record<string, unknown>,
1111
TConfig extends PagesOrInfiniteConfig = PagesOrInfiniteConfig,
1212
>(
13+
/**
14+
* The parameters will be passed to the fetcher.
15+
*/
1316
params: Params,
17+
/**
18+
* A Promise returning function to fetch your data.
19+
*/
1420
fetcher: ((p: Params) => FetcherReturnData | Promise<FetcherReturnData>) | undefined,
21+
/**
22+
* Internal configuration of the hook.
23+
*/
1524
config: TConfig,
1625
cacheKeys: CacheKeys,
1726
) => PaginatedResources<ExtractData<FetcherReturnData>, TConfig['infinite']>;

packages/shared/src/react/hooks/usePagesOrInfinite.rq.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use client';
22

3-
import type { ClerkPaginatedResponse } from '@clerk/types';
43
import { useCallback, useMemo, useRef, useState } from 'react';
54

5+
import type { ClerkPaginatedResponse } from '../../types';
66
import { useClerkQueryClient } from '../clerk-rq/use-clerk-query-client';
77
import { useClerkInfiniteQuery } from '../clerk-rq/useInfiniteQuery';
88
import { useClerkQuery } from '../clerk-rq/useQuery';
@@ -19,8 +19,8 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
1919

2020
const enabled = config.enabled ?? true;
2121
const triggerInfinite = config.infinite ?? false;
22-
// Support keepPreviousData
23-
const _keepPreviousData = config.keepPreviousData ?? false;
22+
// TODO: Support keepPreviousData
23+
// const _keepPreviousData = config.keepPreviousData ?? false;
2424

2525
const [queryClient] = useClerkQueryClient();
2626

packages/shared/src/react/hooks/usePagesOrInfinite.shared.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,31 @@ import { useRef } from 'react';
55
import type { PagesOrInfiniteOptions } from '../types';
66

77
/**
8-
* Shared helper to safely merge user-provided pagination options with defaults.
9-
* Caches initial page and page size for the lifecycle of the component.
8+
* A hook that safely merges user-provided pagination options with default values.
9+
* It caches initial pagination values (page and size) until component unmount to prevent unwanted rerenders.
10+
*
11+
* @internal
12+
*
13+
* @example
14+
* ```typescript
15+
* // Example 1: With user-provided options
16+
* const userOptions = { initialPage: 2, pageSize: 20, infinite: true };
17+
* const defaults = { initialPage: 1, pageSize: 10, infinite: false };
18+
* useWithSafeValues(userOptions, defaults);
19+
* // Returns { initialPage: 2, pageSize: 20, infinite: true }
20+
*
21+
* // Example 2: With boolean true (use defaults)
22+
* const params = true;
23+
* const defaults = { initialPage: 1, pageSize: 10, infinite: false };
24+
* useWithSafeValues(params, defaults);
25+
* // Returns { initialPage: 1, pageSize: 10, infinite: false }
26+
*
27+
* // Example 3: With undefined options (fallback to defaults)
28+
* const params = undefined;
29+
* const defaults = { initialPage: 1, pageSize: 10, infinite: false };
30+
* useWithSafeValues(params, defaults);
31+
* // Returns { initialPage: 1, pageSize: 10, infinite: false }
32+
* ```
1033
*/
1134
export const useWithSafeValues = <T extends PagesOrInfiniteOptions>(params: T | true | undefined, defaultValues: T) => {
1235
const shouldUseDefaults = typeof params === 'boolean' && params;
@@ -33,6 +56,21 @@ export const useWithSafeValues = <T extends PagesOrInfiniteOptions>(params: T |
3356
/**
3457
* Returns an object containing only the keys from the first object that are not present in the second object.
3558
* Useful for extracting unique parameters that should be passed to a request while excluding common cache keys.
59+
*
60+
* @internal
61+
*
62+
* @example
63+
* ```typescript
64+
* // Example 1: Basic usage
65+
* const obj1 = { name: 'John', age: 30, city: 'NY' };
66+
* const obj2 = { name: 'John', age: 30 };
67+
* getDifferentKeys(obj1, obj2); // Returns { city: 'NY' }
68+
*
69+
* // Example 2: With cache keys
70+
* const requestParams = { page: 1, limit: 10, userId: '123' };
71+
* const cacheKeys = { userId: '123' };
72+
* getDifferentKeys(requestParams, cacheKeys); // Returns { page: 1, limit: 10 }
73+
* ```
3674
*/
3775
export function getDifferentKeys(
3876
obj1: Record<string, unknown>,

packages/shared/src/react/hooks/usePagesOrInfinite.swr.tsx

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,29 @@ import { useSWR, useSWRInfinite } from '../clerk-swr';
66
import type { CacheSetter, ValueOrSetter } from '../types';
77
import type { UsePagesOrInfiniteSignature } from './usePageOrInfinite.types';
88
import { getDifferentKeys, useWithSafeValues } from './usePagesOrInfinite.shared';
9+
import { usePreviousValue } from './usePreviousValue';
910

1011
const cachingSWROptions = {
1112
dedupingInterval: 1000 * 60,
1213
focusThrottleInterval: 1000 * 60 * 2,
1314
} satisfies Parameters<typeof useSWR>[2];
1415

16+
/**
17+
* A flexible pagination hook that supports both traditional pagination and infinite loading.
18+
* It provides a unified API for handling paginated data fetching, with built-in caching through SWR.
19+
* The hook can operate in two modes:
20+
* - Traditional pagination: Fetches one page at a time with page navigation
21+
* - Infinite loading: Accumulates data as more pages are loaded.
22+
*
23+
* Features:
24+
* - Cache management with SWR
25+
* - Loading and error states
26+
* - Page navigation helpers
27+
* - Data revalidation and updates
28+
* - Support for keeping previous data while loading.
29+
*
30+
* @internal
31+
*/
1532
export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher, config, cacheKeys) => {
1633
const [paginatedPage, setPaginatedPage] = useState(params.initialPage ?? 1);
1734

@@ -32,8 +49,35 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
3249
pageSize: pageSizeRef.current,
3350
};
3451

52+
const previousIsSignedIn = usePreviousValue(isSignedIn);
53+
54+
// cacheMode being `true` indicates that the cache key is defined, but the fetcher is not.
55+
// This allows to ready the cache instead of firing a request.
3556
const shouldFetch = !triggerInfinite && enabled && (!cacheMode ? !!fetcher : true);
36-
const swrKey = isSignedIn ? pagesCacheKey : shouldFetch ? pagesCacheKey : null;
57+
58+
// Attention:
59+
//
60+
// This complex logic is necessary to ensure that the cached data is not used when the user is signed out.
61+
// `useSWR` with `key` set to `null` and `keepPreviousData` set to `true` will return the previous cached data until the hook unmounts.
62+
// So for hooks that render authenticated data, we need to ensure that the cached data is not used when the user is signed out.
63+
//
64+
// 1. Fetcher should not fire if user is signed out on mount. (fetcher does not run, loading states are not triggered)
65+
// 2. If user was signed in and then signed out, cached data should become null. (fetcher runs and returns null, loading states are triggered)
66+
//
67+
// We achieve (2) by setting the key to the cache key when the user transitions to signed out and forcing the fetcher to return null.
68+
const swrKey =
69+
typeof isSignedIn === 'boolean'
70+
? previousIsSignedIn === true && isSignedIn === false
71+
? pagesCacheKey
72+
: isSignedIn
73+
? shouldFetch
74+
? pagesCacheKey
75+
: null
76+
: null
77+
: shouldFetch
78+
? pagesCacheKey
79+
: null;
80+
3781
const swrFetcher =
3882
!cacheMode && !!fetcher
3983
? (cacheKeyParams: Record<string, unknown>) => {
@@ -53,6 +97,22 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
5397
mutate: swrMutate,
5498
} = useSWR(swrKey, swrFetcher, { keepPreviousData, ...cachingSWROptions });
5599

100+
// Attention:
101+
//
102+
// Cache behavior for infinite loading when signing out:
103+
//
104+
// Unlike `useSWR` above (which requires complex transition handling), `useSWRInfinite` has simpler sign-out semantics:
105+
// 1. When user is signed out on mount, the key getter returns `null`, preventing any fetches.
106+
// 2. When user transitions from signed in to signed out, the key getter returns `null` for all page indices.
107+
// 3. When `useSWRInfinite`'s key getter returns `null`, SWR will not fetch data and considers that page invalid.
108+
// 4. Unlike paginated mode, `useSWRInfinite` does not support `keepPreviousData`, so there's no previous data retention.
109+
//
110+
// This simpler behavior works because:
111+
// - `useSWRInfinite` manages multiple pages internally, each with its own cache key
112+
// - When the key getter returns `null`, all page fetches are prevented and pages become invalid
113+
// - Without `keepPreviousData`, the hook will naturally reflect the empty/invalid state
114+
//
115+
// Result: No special transition logic needed - just return `null` from key getter when `isSignedIn === false`.
56116
const {
57117
data: swrInfiniteData,
58118
isLoading: swrInfiniteIsLoading,
@@ -63,7 +123,7 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
63123
mutate: swrInfiniteMutate,
64124
} = useSWRInfinite(
65125
pageIndex => {
66-
if (!triggerInfinite || !enabled) {
126+
if (!triggerInfinite || !enabled || isSignedIn === false) {
67127
return null;
68128
}
69129

@@ -75,9 +135,9 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
75135
};
76136
},
77137
cacheKeyParams => {
78-
// @ts-ignore - swr provider passes back cacheKey object, compute fetcher params
138+
// @ts-ignore - remove cache-only keys from request params
79139
const requestParams = getDifferentKeys(cacheKeyParams, cacheKeys);
80-
// @ts-ignore - params narrowing deferred to fetcher time
140+
// @ts-ignore - fetcher expects Params subset; narrowing at call-site
81141
return fetcher?.(requestParams);
82142
},
83143
cachingSWROptions,
@@ -160,7 +220,9 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
160220
fetchPrevious,
161221
hasNextPage,
162222
hasPreviousPage,
223+
// Let the hook return type define this type
163224
revalidate: revalidate as any,
225+
// Let the hook return type define this type
164226
setData: setData as any,
165227
};
166228
};

0 commit comments

Comments
 (0)