You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When list params are persisted (in the store or in the URL) with page > 1, remounting the list (e.g. a full page reload, or first navigation into the app) should restore that page. This worked up to react-admin 5.2.x.
What happened instead:
On apps with an authProvider, the persisted page is reset to 1 on every cold mount. On the network you typically see a request for the stored page (e.g. page 3) immediately aborted/superseded by a request for page 1, and the store/URL is overwritten with page: 1.
Why (root cause analysis):
Since 5.3.0, useListController gates the useGetList query on the auth check:
On a cold mount (fresh QueryClient), useAuthenticated()'s query is isPending: true for at least one render, so the list query starts disabled. For a disabled query, TanStack Query reports isFetching: false and data: undefined.
The page-reset effect (unchanged since v4) then fires on the very first commit:
// useListController.tsuseEffect(()=>{if(query.page<=0||(!isFetching&&query.page>1&&(data==null||data?.length===0))){// Query for a page that doesn't exist, set page to 1queryModifiers.setPage(1);return;}
...
},[isFetching,query.page,query.perPage,data,queryModifiers,total]);
!isFetching is true (query disabled), data == null is true (nothing fetched yet), query.page > 1 is true (restored from store/URL) → setPage(1) is dispatched before the data provider was ever called.
The dispatch is deferred by setTimeout(0) inside changeParams (useListParams), while checkAuth usually resolves in a microtask — that's why you often see the request for the stored page fire first and then get replaced by a page-1 request.
The same reset does not happen while navigating inside the app because the ['auth', 'checkAuth'] query is then already cached (isPending: false), so the list query is enabled from its first render and isFetching guards the effect. This makes the bug look intermittent (only reproduces on reload / first mount).
Steps to reproduce:
Configure an Admin with any authProvider whose checkAuth resolves asynchronously (any real-world implementation does):
The list shows page 1; the stored params (and ?page=3 in the URL, when location sync is on) are overwritten with page: 1.
Also reproducible with disableSyncWithLocation + storeKey (params persisted in localStorageStore): the stored page is wiped on every reload.
Suggested fix:
The effect should not treat "query disabled / not yet started" as "page is empty". Guarding with isPending (already returned by useGetList) is enough:
useEffect(()=>{if(isPending)return;// query disabled or first fetch not finishedif(query.page<=0||(!isFetching&&query.page>1&&(data==null||data?.length===0))){queryModifiers.setPage(1);return;}
...
useInfiniteListController has the same pattern and likely needs the same guard.
Workaround:
Passing disableAuthentication (when auth is guaranteed by an outer layer) avoids the disabled window and restores the 5.2.x behavior.
Environment:
React-admin version: 5.15.1 (condition still present on master)
Last version that did not exhibit the issue: 5.2.x (regression introduced in 5.3.0 with access control in controllers, PR Add access control to controllers #10247)
What you were expecting:
When list params are persisted (in the store or in the URL) with
page > 1, remounting the list (e.g. a full page reload, or first navigation into the app) should restore that page. This worked up to react-admin 5.2.x.What happened instead:
On apps with an
authProvider, the persisted page is reset to 1 on every cold mount. On the network you typically see a request for the stored page (e.g. page 3) immediately aborted/superseded by a request for page 1, and the store/URL is overwritten withpage: 1.Why (root cause analysis):
Since 5.3.0,
useListControllergates theuseGetListquery on the auth check:On a cold mount (fresh QueryClient),
useAuthenticated()'s query isisPending: truefor at least one render, so the list query starts disabled. For a disabled query, TanStack Query reportsisFetching: falseanddata: undefined.The page-reset effect (unchanged since v4) then fires on the very first commit:
!isFetchingis true (query disabled),data == nullis true (nothing fetched yet),query.page > 1is true (restored from store/URL) →setPage(1)is dispatched before the data provider was ever called.The dispatch is deferred by
setTimeout(0)insidechangeParams(useListParams), whilecheckAuthusually resolves in a microtask — that's why you often see the request for the stored page fire first and then get replaced by a page-1 request.The same reset does not happen while navigating inside the app because the
['auth', 'checkAuth']query is then already cached (isPending: false), so the list query is enabled from its first render andisFetchingguards the effect. This makes the bug look intermittent (only reproduces on reload / first mount).Steps to reproduce:
Adminwith anyauthProviderwhosecheckAuthresolves asynchronously (any real-world implementation does):?page=3in the URL, when location sync is on) are overwritten withpage: 1.Also reproducible with
disableSyncWithLocation+storeKey(params persisted inlocalStorageStore): the stored page is wiped on every reload.Suggested fix:
The effect should not treat "query disabled / not yet started" as "page is empty". Guarding with
isPending(already returned byuseGetList) is enough:useInfiniteListControllerhas the same pattern and likely needs the same guard.Workaround:
Passing
disableAuthentication(when auth is guaranteed by an outer layer) avoids the disabled window and restores the 5.2.x behavior.Environment: