query icon indicating copy to clipboard operation
query copied to clipboard

Offline mutations are not being paused

Open j-braun0384 opened this issue 3 years ago • 5 comments

Describe the bug

Hi there, first off - want to say thank you for providing this wonderful library. It's been wonderful in development.

In my react native app - I am attempting to use the offline mutation persistence features of v4 (https://tanstack.com/query/v4/docs/guides/mutations#persisting-offline-mutations) however - it seems like, when a device goes offline (whether before or during a mutation), the state of the mutation is not paused but rather immediately errors out which means it is not stored my the persist query client and cannot be resumed at hydration.

Here is my config:

const queryClient = new QueryClient({
  defaultOptions: {
    mutations: {
      cacheTime: STARTING_TIME * 60 * 60 * 24, // 24 hours
    },
  },
})

const asyncStoragePersister = createAsyncStoragePersister({
  storage: AsyncStorage,
  throttleTime: 1000,
})

const persistOptions: PersistQueryClientProviderProps["persistOptions"] = {
  persister: asyncStoragePersister,
  dehydrateOptions: {
    dehydrateMutations: true,
    dehydrateQueries: false
  },
  maxAge: Infinity,
}

const onRestoreSuccess = async () => {
  queryClient.resumePausedMutations()
}

queryClient.setMutationDefaults([MUTATION_KEY], {
  mutationFn: async (mutation) => {
    // ... api call
  },
  onError(error, variables, context) {
    // Toast to show error
    Toast.show({ type: "error", text1: error.message || "mutation error!" })
  },
  onSettled(data, error, variables, context) {},
  onSuccess(data, variables, context) {},
})

// App

export default function App() {
  return (
    <PersistQueryClientProvider client={queryClient} persistOptions={persistOptions} onSuccess={onRestoreSuccess}>
      // ... children
    </PersistQueryClientProvider>
  )
}

// Some component that calls default mutation

const { mutate } = useMutation([MUTATION_KEY])

mutate(payload)

This shows a toast of No Internet Connection every time... is there a config variable i am missing to get offline mutations to pause?

Your minimal, reproducible example

N/A

Steps to reproduce

  1. Configure queryClient & PersistQueryClientProvider as outlined is description
  2. Turn device offline
  3. invoke default mutation

Expected behavior

offline mutations should be paused and dehydrated into persister - which allows them to be resumed via queryClient.resumePausedMutations()

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

React Native

react-query version

v4.0.10

TypeScript version

No response

Additional context

No response

j-braun0384 avatar Sep 14 '22 17:09 j-braun0384

The offline example shows how to do this and it works. If it doesn't work for you, you'd have to provide your own reproduction that doesn't work https://tanstack.com/query/v4/docs/examples/react/offline

TkDodo avatar Sep 14 '22 20:09 TkDodo

I have almost the same issue. I want to resend failed mutations with resumePausedMutations(), but I can't find a way to cache mutations with isPaused: true on error Are there any examples of how to do that?

pavelbabenko avatar Sep 15 '22 15:09 pavelbabenko

yes, this is the working example: https://tanstack.com/query/v4/docs/examples/react/offline

if you find something that is not working in this example, let me know.

TkDodo avatar Sep 15 '22 15:09 TkDodo

@TkDodo The example you sent is caching mutations with isPaused: true only when there is no internet connection. When mutation finishes with error (event network error), it is cached with isPaused: false and resumePausedMutations() doesn't resend them

pavelbabenko avatar Sep 16 '22 10:09 pavelbabenko

@pavelbabenko yes because the mutation finished in an erroneous state and is thus not "resumable" anymore. It could've also failed with a 500 error; Once a query is in error state, it is "done" from a fetching point of view.

The most likely scenario where I can see this happen is when you have connection, then start the mutation, then the connection goes away while you were doing the mutation? Otherwise, if you are already offline, it shouldn't even have started with networkMode: 'online'.

The best way to handle this scenario is to set retry: 1 or to a function that performs a retry if there is a NetworkError. In that case, if the mutation fails, it will be paused before the retry happens, thus it is also resumable with resumePausedMutations.

TkDodo avatar Sep 16 '22 13:09 TkDodo

I've recently implemented this myself and since I'm pretty new to React Native I also had some trouble getting it all working. I've managed to get it working now and created an example repo hopefully this can help someone else.

I had some issues with Android / iPhone simulators not reconnecting back to the internet properly (causing mutations to retrigger but getting a network error), and I also experienced some persistance issues (atleast on Android). So I would recommend testing on a physical device.

fedorish avatar Dec 03 '22 12:12 fedorish

@fedorish your example repo looks really good 👏

one thing that would potentially correlate to your issues is that you re-create the QueryClient and the persister during rendering (code pointer).

So if App re-renders, you'll get a new QueryClient and thus a new cache, and a new persister ...

You'd want to either create these things outside of the App component, or inside of a useState lazy initializer. I have a blogpost on this topic:

  • https://tkdodo.eu/blog/use-state-for-one-time-initializations

TkDodo avatar Dec 03 '22 13:12 TkDodo

@TkDodo

Thank you! 🙏🏼

Also thank you for the suggestion, now when you mention it, it's so obvious 😅 I think it's outside of App.js in the official examples aswell, so not sure how I managed to miss that. I've pushed a fix! 👍🏽

This seems to have fixed the Android persistance issue, but iPhone simulators still gives network errors. The mutation runs and it passes the variables correctly, but it errors with Network request failed, and I think I read somewhere that there is this issue with iPhone simulators having issues with connecting back to the internet.

I'm not sure how they work but I assume the device sends a signal that it's connected to the internet before its "fully connected" and therefore network requests fails.

fedorish avatar Dec 03 '22 15:12 fedorish