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
- In React-Query version 5, the
useMutation
hook returnsisPending
instead ofisLoading
. Initially, I misconceivedisPending
solely as indicative of submission loading. However, it signifies both loading states: when online (isLoading
) and when offline (isPending
). This is evident by assessingisPaused
withinuseMutation
, 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
Resume Paused Mutations
In my scenario, it appeared that none of the mutations were being paused. Here is the resolution I implemented:
- 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()
}
})
}}
>
- 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
Top comments (0)