Eliminating Repetitive Loading State Handling in Mutations with @tanstack/react-query
Note that when I mention “react-query”, I’m referring to
@tanstack/react-query
If you are working in mobile apps, you must be familiar with this UI Pattern.
As you can see, we present a full loading screen to the user to prevent unwanted actions such as going back, resubmitting a form, and canceling an action when we are submitting data or mutating data to our server.
Usually, we would do something like this:
const LoginFormScreen = () => {
const [isLoading, setIsLoading] = useState(false)
const onSubmit = () => {
setIsLoading(true)
promise.finally(() => {
setIsLoading(false)
})
}
return (
<>
<LoginForm onSubmit={() => } />
<FullScreenLoading isLoading={isLoading} />
</>
)
}
The issue with the code above is that you need to repeat handling the state over and over again every time you have a mutation action. This could be very daunting if you have a lot of forms in your app.
We can cut some of the repeating stuff by using react-query
const LoginFormScreen = () => {
const { mutate, isPending } = useMutation(() => {
mutationFn: () => promise
})
const onSubmit = () => {
mutate()
}
return (
<>
<LoginForm onSubmit={() => } />
<FullScreenLoading isLoading={isPending} />
</>
)
}
Everything is so much cleaner, but still, every time we have a new form, we will repeat the same task over and over again.
What if we can do these one time and never need to worry about it anymore?
Fortunately, react-query
tracks how many mutation is currently running. This means that we have access to the running mutation amount everywhere in our apps as long as it is inside the QueryClientProvider
and we can access isMutating
state globally with useIsMutating
hook provided by react-query
:
import { useIsMutating } from "@tanstack/react-query";
export const FullScreenLoading = () => {
const isMutating = useIsMutating();
return (
<Modal visible={!!isMutating} transparent>
<View style={styles.container}>
<View style={styles.loadingContainer}>
<ActivityIndicator />
</View>
</View>
</Modal>
);
};
We eliminate the need for FullScreenLoading
to have props, and we just need to place FullScreenLoading
component at the top level of our App
and never think about handling this again:
const App = () => {
return (
<>
<TheRestOfYourApp />
<FullScreenLoading />
</>
);
};
export default App;
Additionally we can pass mutationKey
to useIsMutating({ mutationKey })
in case we only want to show Full Loading Screen for specific mutation.
Make sure you also pass mutationKey
in your useMutation call
useMutation({ mutationKey, mutationFn });
Shared Mutation Cache
In @tanstack/react-query
v5 useMutationstate
was introduced to solve this https://github.com/TanStack/query/issues/2304
Not that now we have access to the mutation loading state, we are now have access to data
and error
by filtering mutation by key or status
const variables = useMutationState({
filters: { status: "pending" },
select: (mutation) => mutation.state.variables,
});
const isMutating = variables.length > 0;
As you can see we can even achieve the same behavior as what useIsMutating
have