React Query Mutations Offline React-Native
Tags: reactnative, offline, reactquery, mutations
Published: 2024-02-28 00:26:47

## Introduction
We are addressing a React-Query mutation issue related to offline functionality. Upon analysis, several discrepancies were identified within the setup. The following points elucidate the observed issues:

### Paused & Pending
1. In React-Query version 5, the `useMutation` hook returns `isPending` instead of `isLoading`. Initially, I misconceived `isPending` solely as indicative of submission loading. However, it signifies both loading states: when online (`isLoading`) and when offline (`isPending`). This is evident by assessing `isPaused` within `useMutation`, which reflects false when online and true when offline.

`isPaused` indicates that the request has been cached and will resume (if configured accordingly) upon device reconnection to the internet.

```
const {
    reset,
    mutate,
    isPaused,
    isPending,
  } = useCreate();

useEffect(() => {
    if (isPaused) {
      // If a request is pending due to offline work, it may be necessary to reset the form to allow the action to be attempted multiple times despite the offline status.
      reset();
    }
  }, [isMutationPaused]);
```

### Storing (updating) Offline Data
Using react query eliminated the need of redux to data it already hold, you will store the data `onMutate` and here's how it done.

Remember the queryKey you assigned to the data `e.g ['todos']`

``` 
const useCreate = () => {
  const queryClient = useQueryClient();
  return useMutation({
    onMutate: async variables => {
      // Cancel current queries for the order list
      await queryClient.cancelQueries({ queryKey: ['todos] })

      // Create optimistic todos
      const optimisticTodo = { ...variables };

      // Add optimistic todo to other todos list
      queryClient.setQueryData(
        ['todos'],
        (previousData) => {
          return [...previousData, optimisticTodo ];
        },
      );

      // Return context with the optimistic todo
      return { optimisticTodo };
    },
    mutationFn: (data) => createOrder(data),
  });
};
```

for more info read about Mutations on [React_Query_Docs](https://tanstack.com/query/latest/docs/framework/react/guides/mutations)

### Resume Paused Mutations
In my scenario, it appeared that none of the mutations were being paused. Here is the resolution I implemented:

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()
                    }
                })
            }}
        >
```
2. 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')
        }
    })
```

### Honorable mention
If you persist offline mutations with the persistQueryClient plugin, mutations cannot be resumed when the page is reloaded unless you provide a default mutation function.

This is a technical limitation. When persisting to an external storage, only the state of mutations is persisted, as functions cannot be serialized. After hydration, the component that triggers the mutation might not be mounted, so calling resumePausedMutations might yield an error: No mutationFn found.

```
const persister = createSyncStoragePersister({
  storage: window.localStorage,
})
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 1000 * 60 * 60 * 24, // 24 hours
    },
  },
})

// we need a default mutation function so that paused mutations can resume after a page reload
queryClient.setMutationDefaults(['todos'], {
  mutationFn: ({ id, data }) => {
    return api.updateTodo(id, data)
  },
})

export default function App() {
  return (
    <PersistQueryClientProvider
      client={queryClient}
      persistOptions={{ persister }}
      onSuccess={() => {
        // resume mutations after initial restore from localStorage was successful
        queryClient.resumePausedMutations()
      }}
    >
      <RestOfTheApp />
    </PersistQueryClientProvider>
  )
}
```

### Conclusion
I trust you found this information beneficial. For further details, please refer to the conversation on this GitHub issue [here](https://github.com/TanStack/query/issues/5847)