@@ -6,12 +6,29 @@ import { useSWR, useSWRInfinite } from '../clerk-swr';
66import type { CacheSetter , ValueOrSetter } from '../types' ;
77import type { UsePagesOrInfiniteSignature } from './usePageOrInfinite.types' ;
88import { getDifferentKeys , useWithSafeValues } from './usePagesOrInfinite.shared' ;
9+ import { usePreviousValue } from './usePreviousValue' ;
910
1011const 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+ */
1532export 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