Skip to content

useListController resets stored page to 1 on mount while the list query is disabled during authProvider.checkAuth (regression in 5.3.0) #11297

Description

@ethembynkr

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 with page: 1.

Why (root cause analysis):

Since 5.3.0, useListController gates the useGetList query on the auth check:

// useListController.ts
enabled:
    (!isPendingAuthenticated && !isPendingCanAccess) ||
    disableAuthentication,

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.ts
useEffect(() => {
    if (
        query.page <= 0 ||
        (!isFetching &&
            query.page > 1 &&
            (data == null || data?.length === 0))
    ) {
        // Query for a page that doesn't exist, set page to 1
        queryModifiers.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:

  1. Configure an Admin with any authProvider whose checkAuth resolves asynchronously (any real-world implementation does):
const authProvider = {
    checkAuth: () => new Promise<void>(resolve => setTimeout(resolve, 200)),
    checkError: () => Promise.resolve(),
    login: () => Promise.resolve(),
    logout: () => Promise.resolve(),
};

<Admin dataProvider={dataProvider} authProvider={authProvider}>
    <Resource name="posts" list={PostList} />
</Admin>
  1. Open the list, navigate to page 3.
  2. Reload the browser (F5).
  3. 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 finished
    if (
        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)
  • React version: 18.x
  • Browser: any

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions