query: resumePausedMutations are not running after switching from offline to online after version 4.24.3 up to 4.32.6(latest)

Describe the bug

https://github.com/TanStack/query/commit/8991d7c0193c91fb17a97998bdbcda4363943f35 this is the commit where it breaks (4.24.3)

Not sure if this is correct behaviour. But based on the commit above i assume it is a bug indeed. Paused mutations get stuck when rehydrating(loading) mutations in offline mode and switching back online.

Basically, when you reenter the exited app in offline mode it restores the mutations successfully, i see paused mutations in logs as a result of resumePausedMutations.

But when you get back online, paused mutations do not run. This happens in latest 4.32.6. When i reload(React Native) my javascript it runs again. But, i got to the version where it works and triaged this. basically this is the commit where it starts breaking https://github.com/TanStack/query/commit/8991d7c0193c91fb17a97998bdbcda4363943f35

Shortly, 4.24.3 and upper version causes this bug

Your minimal, reproducible example

Run https://github.com/TanStack/query/tree/main/examples/react/offline

Steps to reproduce

  1. Go to any movie
  2. Go offline/ turn off your wife
  3. Add comment (to verify, add it three times)
  4. Refresh the page
  5. ENABLE your WIFI and then wait
  6. Nothing happens, but expected this mutations to get unpaused
  7. Refresh the page and you see updates are applied now sequentially.

This happens in my React Native setup too.

  1. Go offline
  2. Add todo (fire any mutation which will be paused)
  3. Exit from app then enter again (both RN debug/release mode)
  4. enable internet
  5. At the end i see 1 paused mutation pending in my logs. So it stucks/waits here:
queryClient.resumePausedMutations()

Expected behavior

As a user, i am expecting all mutations to start running after resume, but no mutations are running in that case. In logs they are all paused

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

React Native 0.70.6

Tanstack Query adapter

react-query

TanStack Query version

4.32.6 (but started in 4.24.3!!)

TypeScript version

No response

Additional context

If i will reload my javascript after 5th step, then it resumes mutations again and runs them correctly

Also, if you have internet enabled before entering the app, it works as expected.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 9
  • Comments: 31 (3 by maintainers)

Most upvoted comments

lool, was readin my serious description over there to refresh my mind: 2. turn off your wife

šŸ˜‚

Sorry, that might take some time for me to write testcase now, but i already found the commit which is causing this issue, hope someone will help here. Will try to reproduce this with test case in case if i will have a time

thanks for this, I think I figured it out a bit:

when we resume paused mutations after you’ve reloaded the page, there is no ā€œretryerā€ that can pick up the mutations from where they left, so we just always execute them:

https://github.com/TanStack/query/blob/13b17ee45de3299dd3d3a2dfc3d23ad2f024882a/packages/query-core/src/mutation.ts#L162

This will then create the retryer and return a pending promise, because we start in paused state. This is technically fine, however, since we started this process via resumePausedMutations, we have now stored that promise in the mutationCache in this.resuming:

https://github.com/TanStack/query/blob/13b17ee45de3299dd3d3a2dfc3d23ad2f024882a/packages/query-core/src/mutationCache.ts#L164

This was the fix that made sure that resuming paused mutations will always run in serial.

Now when you go online again, we call resumePausedMutations again, but it doesn’t actually resume anything, because it already thinks it is resuming. This works when we have a retyer, because retryer.continue() resolves a Promise immediately if we are not online (another part of the aforementioned fix):

https://github.com/TanStack/query/blob/6fcfc9bfba0b56d580952c2c22e91ca45c45cd17/packages/query-core/src/retryer.ts#L212-L215

but it doesn’t work if you are offline when the page loads.


I need to think a bit how to tackle this holistically, but what you can do on your end if to simply wrap the call to resumePausedMutations into another online check:

 <PersistQueryClientProvider
   client={queryClient}
   persistOptions={{ persister }}
   onSuccess={() => {
     // resume mutations after initial restore from localStorage was successful
+      if (onlineManager.isOnline()) {
           queryClient.resumePausedMutations().then(() => {
               queryClient.invalidateQueries()
           })
+      }
   }}
 >

We should probably do this internally, but I have to think about where exactly without breaking things. Also, this should be significantly less relevant with v5 because the onlineManager will always start in online mode.

I was laughing hard at that one, too 🤣

@TkDodo thanks for the investigation & explanation, that all makes sense! i can confirm the workaround works with the latest v5 beta

[edit] The workaround works in the repro, but i’m still having problems when using the beta in React Native. I don’t understand the difference but i’ll update this thread if i figure it out.

@TkDodo in the meantime i’ve create a repro here, see README for instructions: https://github.com/badsyntax/react-query-mutation-resume-bug

i’ll see what i can do with replay.io

I’m having the same issue in a react-native/react-native-web + react-query project using react-query version 4.29.7. Tried rolling back to 4.24.2 and I confirm that the issue is not present in that version.

@badsyntax Ok, You were right šŸ‘ This was good direction, basically I had to check if I’m online using NetInfo library but in 2 places:

  1. To try to resume paused mutations only if online otherwise it will also throw Network Error and mutation will no longer be paused.
        <PersistQueryClientProvider
            client={queryClient}
            persistOptions={{
                persister: syncStoragePersister
            }}
            onSuccess={() => {
                NetInfo.fetch().then(state => {
                    if (state.isConnected) {
                        queryClient.resumePausedMutations()
                    }
                })
            }}
        >
  1. Setting focus manager also only if device is only otherwise this will also try to trigger mutations and if offline they will fail with NetworkError so no longer paused:
    NetInfo.fetch().then(state => {
        if (state.isConnected && Platform.OS !== 'web') {
            focusManager.setFocused(status === 'active')
        }
    })

For the focus manager I think there might be better way to handle this I just posting it for now like this. With this it looks like all 3 cases from my previous post works šŸ‘

The Code Sandbox version isn’t working for me either (https://tanstack.com/query/v4/docs/react/examples/react/offline) but when running it locally with @timabilov’s steps, am able to reproduce

To reproduce:

  1. Run https://github.com/TanStack/query/tree/main/examples/react/offline
  2. Go to any movie
  3. Go offline/ turn off your wife
  4. Add comment (to verify, add it three times)
  5. Refresh the page
  6. ENABLE your WIFI and then wait
  7. Nothing happens, but expected this mutations to get unpaused
  8. Refresh the page and you see updates are applied now sequentially.

I think it important to note that the issue happens when turning off the computer’s wifi (or wife!) in the steps above and refreshing the page while the wifi is still disabled.

Disabling the connection via the Devtools doesn’t reproduce the issue for me, perhaps because the connection is automatically restored when refreshing?

Also experiencing this or something similar on React Native with v4.24.6 of query, the query client, and async storage persister.

To test, we do the following:

  1. Turn the device to airplane mode or turn off wifi + data
  2. Perform a mutation. Observe with useIsMutating() that mutation count is > 0
  3. Remove the app from background
  4. With airplane mode still enabled, relaunch app
  5. Turn off airplane mode, observe that app and queryClient go online, but mutation is never POSTed, mutation count remains > 0

If we turn off airplane mode prior to relaunching the app, the mutation is is sent as expected.

We have the same issue with React Web + React Native.

resumePausedMutations() {
  if (!this.resuming) {
    const pausedMutations = this.mutations.filter(x => x.state.isPaused);
    this.resuming = notifyManager.notifyManager.batch(() => pausedMutations.reduce((promise, mutation) => promise.then(() => mutation.continue().catch(utils.noop)), Promise.resolve())).then(() => {
      this.resuming = undefined;
    });
  }

  return this.resuming;
}

So, i found that this is related to the fact that i have multiple setOnline(true) calls, and because of that ā€˜resuming’ patch - mutation.continue() only fired once where it basically still stays in pause. But without this resuming patch, subsequent setOnline(true) -> resumePausedMutations calls fires my requests.

To be sure, it is definitely not request hanging or some timeout issues because my mutationFn is not called at all with given conditions.

Not sure if it is related to something with my connection anyway, because after setOnline(True) it still should trigger mutations, which is weird. but i see only resumePausedMutations triggers