reduxjs / redux-toolkit

The official, opinionated, batteries-included toolset for efficient Redux development
https://redux-toolkit.js.org
MIT License
10.76k stars 1.18k forks source link

RTK Query gets stuck in "pending" state when following the persistence example from the website #4715

Open GertSallaerts opened 2 weeks ago

GertSallaerts commented 2 weeks ago

Problem description

When you set up your store to persist your api reducer as outlined on the website, there's a chance your persisted queries get stuck in "pending" state if the app gets interrupted while fetching a query (e.g. the window is reloaded.) This only happens when a few conditions are met:

  1. You've set up redux-persist to persist the api reducer, not the root reducer.
  2. None of the queries that get rehydrated are fulfilled or rejected, e.g. they were all pending when the app got interrupted
  3. You're using autoMergeLevel1 as state reconciler for redux persist
  4. You're using extractRehydrationInfo in your api slice as documented on the website

When both those condition are met, the following will happen upon rehydration:

  1. The app loads, state is initialized, etc.
  2. The current state of your api slice looks a bit like this: { queries: {}, mutations: {}, config: {...} }
  3. redux-persist's REHYDRATE action is triggered
  4. Our extractRehydrationInfo runs and returns the REHYDRATE payload to RTKQ
  5. RTKQ takes that payload and sets all queries that are either fulfilled or rejected on the queries object. In our case that's none of them (condition 2 above.) This happens here: https://github.com/reduxjs/redux-toolkit/blob/24286f1745e42217448de1f3486886fa8558c1b5/packages/toolkit/src/query/core/buildSlice.ts#L390-L399
  6. At the end of REHYDRATE, redux-persist will use autoMergeLevel1 to do a shallow compare between the state before and after REHYDRATE. It will not detect any changes to the queries subkey of the state at this point and resort to retaining what it loaded from storage (i.e. the pending queries.) This happens here: https://github.com/rt2zz/redux-persist/blob/d8b01a085e3679db43503a3858e8d4759d6f22fa/src/stateReconciler/autoMergeLevel1.ts#L24
  7. RTKQ will not load the data for those queries anymore because (since they're in pending state) it's waiting for them to complete which they will never do.

Example

I've set up a minimal example that shows the issue here: https://codesandbox.io/p/sandbox/qpv4m9

Inside the getPokemonByName endpoint, it does a window.reload() to simulate the user reloading the page. So if the code worked, the example would be in an infinite reload loop. Instead it will rehydrate the pending query from localStorage after the first reload and get stuck there.

Towards a solution

I don't think there's a bug in redux-persists, nor in RTK Query. Both work as advertised by themselves.

The problem is that the example code on the RTK website on how to make them work together doesn't work in all scenarios. I can think of two ways to resolve this:

I'm happy to help out on either one but I'd be interested in hearing the team's thoughts on this first.

markerikson commented 6 days ago

Yeah, persistence is outside the scope of RTK, so better documentation is the best answer here.