Skip to content

useCanAccess throws Maximum update depth exceeded on long lists under React 19 #11392

Description

@paour

What you were expecting:

A list page with 100 rows renders without errors under React 19.

What happened instead:

The first load of a list page with perPage={100} throws Maximum update depth exceeded one to three times. The page recovers, and every row ends up with its links, but each error is uncaught and reaches error monitoring. The error also occurs without <React.StrictMode>.

During one such load, the react-query cache holds about 250 ['auth', 'canAccess', …] queries: several per row, for rowClick and for the link of each <ReferenceField>.

Cause:

useCanAccess keys its query on recordId, so every row owns a distinct query, and every authProvider.canAccess() promise resolves on its own. react-query then flushes one observer notification per query, each in its own setTimeout(0) task, and React commits each of them separately.

React 19 counts a commit as a nested update when it leaves a Sync or Default lane pending. React 18 counted only a pending Sync lane (includesSomeLane(remainingLanes, SyncLane)). While an update scheduled by an earlier commit is still pending, these per-query commits add up and pass the limit of 50.

This is the mechanism that #11329 fixed for useGetManyAggregate, and #11379 for useFormGroup. useCanAccess is not covered yet.

Steps to reproduce:

  1. Use an authProvider whose canAccess is async. Ours awaits a cached current-user promise, then checks the user's roles.
  2. Render a list with perPage={100}, rowClick, and a few <ReferenceField> columns.
  3. With React 19.3, load the list page for the first time, so that no access check is cached.

I have not reduced this to a sandbox yet.

Suggested fix:

Apply the batching of resolveCallsWithData to useCanAccess: the checks that settle in the same tick write their result with setQueryData inside one notifyManager.batch, then their promises resolve. We run this change as a local patch on ra-core 5.15.4. With it, both pages we measured load without the error, and every canAccess query ends in status: 'success', fetchStatus: 'idle'. I will open a pull request with it.

Other information:

At the time of the error, the stack runs from the react-query notifyManager flush to the useSyncExternalStore store change, then forceStoreRerender.

Environment

  • React-admin version: 5.15.4
  • Last version that did not exhibit the issue (if applicable): none known. We hit this while moving from React 18.3.1 to 19.3.0, and did not test this page on React 18.
  • React version: 19.3.0, with @tanstack/react-query 5.102.8
  • Browser: Chrome
  • Stack trace (in case of a JS error): Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions