Skip to content

RTK Query middleware self-registration can overflow the call stack with many createApi middlewares #5279

@PabloJoan

Description

@PabloJoan

Summary

I am seeing a RangeError: Maximum call stack size exceeded failure in RTK Query when a store contains a large number of RTK Query middlewares and the first query dispatch triggers middleware self-registration.

From tracing the failure, this appears to come from RTK Query's internal middlewareRegistered dispatch path re-entering the full middleware chain via mwApi.dispatch for every uninitialized API middleware. With enough createApi instances, the resulting nested cascade appears to grow roughly quadratically and can overflow the browser call stack.

In practice, this presents as:

  • a browser console RangeError
  • the query failing to initialize correctly
  • config.middlewareRegistered never reaching a usable state for the affected API
  • the component staying stuck in loading or empty-data state

I realize that 100+ createApi instances is not a common setup, but the current behavior is a hard runtime failure and the failure mode is difficult to diagnose because the overflow is thrown deep inside RTK Query internals during thunk execution.

Reproduction repo

https://github.com/PabloJoan/break-rtk

The repo includes:

Versions

  • @reduxjs/toolkit: 2.11.2
  • react: 18.1.0
  • react-dom: 18.1.0
  • react-redux: 8.0.2
  • react-scripts: 5.0.1
  • typescript: 4.2.4
  • OS: macOS

Steps to reproduce

  1. Clone the reproduction repo: https://github.com/PabloJoan/break-rtk
  2. Run npm ci
  3. Run npm start
  4. Open the app in the browser
  5. Let the initial RTK Query request run
  6. Open the browser console

Actual behavior

The first query dispatch triggers a stack overflow during RTK Query middleware initialization.

Console excerpt:

redux-thunk.mjs:12 Uncaught RangeError: Maximum call stack size exceeded
    at Module.isAction (redux-thunk.mjs:12:1)
    at index.ts:51:1
    at index.ts:67:1
    at index.ts:67:1
    at index.ts:67:1
    at redux-thunk.mjs:7:1
    at actionCreatorInvariantMiddleware.ts:29:1
    at Object.dispatch (applyMiddleware.ts:52:1)
    at index.ts:57:1
    at index.ts:67:1
    ... many repeated nested middleware frames continue ...

Observed state/behavior after the failure:

  • the query does not complete successfully
  • the RTK Query registration path does not fully finish
  • the UI remains in a loading or no-data state

Expected behavior

RTK Query middleware initialization should not be able to overflow the JavaScript call stack simply because many API middlewares are present in the store.

Even if the number of APIs is unusually high, I would expect the initialization path to be bounded and non-recursive enough that:

  • the first query can initialize cleanly, or
  • RTK Query surfaces a deterministic, actionable error instead of failing deep inside a nested internal dispatch chain

Why I think this happens

My understanding of the internal flow is:

  1. Every createApi instance contributes its own RTK Query middleware.
  2. Each middleware has its own initialized closure flag.
  3. On the first action it sees, the middleware sets initialized = true and dispatches api.internalActions.middlewareRegistered(...).
  4. That dispatch uses mwApi.dispatch, so it re-enters the full composed store dispatch from the top rather than continuing with next.
  5. The next uninitialized RTK Query middleware then does the same thing.

So the first action effectively produces a nested cascade like this:

dispatch(action A)
  -> MW1 sees first action
    -> mwApi.dispatch(middlewareRegistered1)
      -> full chain restarts
        -> MW2 sees first action
          -> mwApi.dispatch(middlewareRegistered2)
            -> full chain restarts
              -> MW3 sees first action
                -> mwApi.dispatch(middlewareRegistered3)
                  -> ... repeats ...

Because each nested dispatch traverses all previously initialized middlewares before reaching the next uninitialized one, the amount of nested work appears to scale roughly as O(N^2).

Relevant source references:

I also think the failure threshold is easier to hit when the first action is a query thunk, because thunk and createAsyncThunk add extra frames around the RTK Query internal dispatch chain. In my traces, the RangeError is thrown inside that cascade and then the thunk flow catches it and continues down a rejected path, which makes the resulting behavior look more like a failed query than an obvious middleware initialization bug.

Workaround / mitigation I tested

I was able to mitigate the problem locally by prepending a middleware that flattens nested middlewareRegistered dispatches.

The workaround does this:

  1. track whether a dispatch is already in progress
  2. if a nested RTK Query middlewareRegistered action appears during that dispatch, queue it instead of letting it recurse immediately
  3. let the outer action finish
  4. flush the queued middlewareRegistered actions sequentially afterward

That avoids the deep recursive cascade and reduces the initialization shape from approximately O(N^2) nested dispatch depth to O(N) sequential processing.

The reason this appears to work is that RTK Query sets initialized = true before dispatching middlewareRegistered, so by the time the queued action is replayed later, that middleware no longer tries to trigger a fresh registration cascade.

I am not attached to this exact workaround as an upstream solution, but it suggests that flattening or otherwise de-recursing the self-registration path would avoid the overflow.

Question

Is this considered a bug or known scalability limitation in RTK Query?

If it is a bug, would you be open to changing the middleware self-registration flow so that it does not recursively re-enter the full dispatch chain for each uninitialized API middleware on the first action?

If helpful, I can refine the reproduction further or reduce it to a smaller standalone store setup focused only on the middleware initialization path.

Pinned by aryaemami59

Metadata

Metadata

Assignees

Labels

PerformanceIssues regarding performanceRTK-QueryIssues related to Redux-Toolkit-Query
No fields configured for Enhancement.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions