Skip to content

Commit b7bb779

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

File tree

5 files changed

+102
-348
lines changed

5 files changed

+102
-348
lines changed

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: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ 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,
@@ -32,8 +33,35 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
3233
pageSize: pageSizeRef.current,
3334
};
3435

36+
const previousIsSignedIn = usePreviousValue(isSignedIn);
37+
38+
// cacheMode being `true` indicates that the cache key is defined, but the fetcher is not.
39+
// This allows to ready the cache instead of firing a request.
3540
const shouldFetch = !triggerInfinite && enabled && (!cacheMode ? !!fetcher : true);
36-
const swrKey = isSignedIn ? pagesCacheKey : shouldFetch ? pagesCacheKey : null;
41+
42+
// Attention:
43+
//
44+
// This complex logic is necessary to ensure that the cached data is not used when the user is signed out.
45+
// `useSWR` with `key` set to `null` and `keepPreviousData` set to `true` will return the previous cached data until the hook unmounts.
46+
// So for hooks that render authenticated data, we need to ensure that the cached data is not used when the user is signed out.
47+
//
48+
// 1. Fetcher should not fire if user is signed out on mount. (fetcher does not run, loading states are not triggered)
49+
// 2. If user was signed in and then signed out, cached data should become null. (fetcher runs and returns null, loading states are triggered)
50+
//
51+
// 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.
52+
const swrKey =
53+
typeof isSignedIn === 'boolean'
54+
? previousIsSignedIn === true && isSignedIn === false
55+
? pagesCacheKey
56+
: isSignedIn
57+
? shouldFetch
58+
? pagesCacheKey
59+
: null
60+
: null
61+
: shouldFetch
62+
? pagesCacheKey
63+
: null;
64+
3765
const swrFetcher =
3866
!cacheMode && !!fetcher
3967
? (cacheKeyParams: Record<string, unknown>) => {
@@ -53,6 +81,22 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
5381
mutate: swrMutate,
5482
} = useSWR(swrKey, swrFetcher, { keepPreviousData, ...cachingSWROptions });
5583

84+
// Attention:
85+
//
86+
// Cache behavior for infinite loading when signing out:
87+
//
88+
// Unlike `useSWR` above (which requires complex transition handling), `useSWRInfinite` has simpler sign-out semantics:
89+
// 1. When user is signed out on mount, the key getter returns `null`, preventing any fetches.
90+
// 2. When user transitions from signed in to signed out, the key getter returns `null` for all page indices.
91+
// 3. When `useSWRInfinite`'s key getter returns `null`, SWR will not fetch data and considers that page invalid.
92+
// 4. Unlike paginated mode, `useSWRInfinite` does not support `keepPreviousData`, so there's no previous data retention.
93+
//
94+
// This simpler behavior works because:
95+
// - `useSWRInfinite` manages multiple pages internally, each with its own cache key
96+
// - When the key getter returns `null`, all page fetches are prevented and pages become invalid
97+
// - Without `keepPreviousData`, the hook will naturally reflect the empty/invalid state
98+
//
99+
// Result: No special transition logic needed - just return `null` from key getter when `isSignedIn === false`.
56100
const {
57101
data: swrInfiniteData,
58102
isLoading: swrInfiniteIsLoading,
@@ -63,7 +107,7 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
63107
mutate: swrInfiniteMutate,
64108
} = useSWRInfinite(
65109
pageIndex => {
66-
if (!triggerInfinite || !enabled) {
110+
if (!triggerInfinite || !enabled || isSignedIn === false) {
67111
return null;
68112
}
69113

@@ -75,9 +119,9 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
75119
};
76120
},
77121
cacheKeyParams => {
78-
// @ts-ignore - swr provider passes back cacheKey object, compute fetcher params
122+
// @ts-ignore - remove cache-only keys from request params
79123
const requestParams = getDifferentKeys(cacheKeyParams, cacheKeys);
80-
// @ts-ignore - params narrowing deferred to fetcher time
124+
// @ts-ignore - fetcher expects Params subset; narrowing at call-site
81125
return fetcher?.(requestParams);
82126
},
83127
cachingSWROptions,
@@ -160,7 +204,9 @@ export const usePagesOrInfinite: UsePagesOrInfiniteSignature = (params, fetcher,
160204
fetchPrevious,
161205
hasNextPage,
162206
hasPreviousPage,
207+
// Let the hook return type define this type
163208
revalidate: revalidate as any,
209+
// Let the hook return type define this type
164210
setData: setData as any,
165211
};
166212
};

0 commit comments

Comments
 (0)