React TanStack Query - An async state management library
React TanStack Query - An async state management library

React TanStack Query - An async state management library

Feb 2, 2024

Why use React Query?

React Query simplifies data fetching, synchronization, and updating async states in React.
Provides hooks and utilities to manage complicated stuff related to API calls and updates in a react application, simplifies stuff like caching and automatic refetching :]

Super simple example of fetching plans from the backend using useQuery hook

interface Params { id: string } export const getPlans = async ({ id }: Params) => { if (!id) return []; const endpoint = `${resumeId}`; const headers = new Headers(); const authToken = await auth.currentUser?.getIdTokenResult(true); headers.append('Authorization', `Bearer ${authToken?.token}`); const res = await window.fetch(endpoint, { method: 'GET', headers: headers, }); const data = await res.json(); return data; }; export const useGetPlans = (params: Params) => { return useQuery({ queryKey: ['plans', params], // Dependency array, when params change, this query is automatically fetched queryFn: () => getPlans(params), }); };

How it will be used in the frontend:

export default function DisplayPlans() { const { data: plans, isLoading } = useGetPlans({ id: selectedSomething?.id, }); return ( // display plans here <> {isLoading ? ( <LoadingComponent /> ) : ( <div className="flex w-full flex-col items-start gap-2 self-stretch transition-height duration-500"> {plans?.map((plan: IPlan) => { // Display the plans } })} </div> )} </> ) }

Retrieving (fetching) data: useQuery

Once you define the dependencies, React Query takes care of fetching the data (along with performing smart updates whenever necessary), making sure that the data in the backend is the same as (in sync with) data in the front-end

Updating data: useMutation

Mutations are for updating data in the backend.
Changes made by the mutation aren’t coupled to the queries, so that has to be done manually by invalidating the queries which will automatically fetch the new data from the backend.

Code example from

import { useMutation, useQueryClient } from 'react-query'; import axios from 'axios'; const useAddComment = (id: number) => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (newComment: any) =>`/posts/${id}/comments`, newComment), onSuccess: () => { // ✅ refetch the comments list for our blog post queryClient.invalidateQueries({ queryKey: ['posts', id, 'comments'] }); }, }); }; export default useAddComment;
Its also possible to do direct updates to the query cache like:
onSuccess: (newPost) => { queryClient.setQueryData(['posts', id], newPost) }
onSuccess: (newPost) => {
11 // ✅ update detail view directly
12 queryClient.setQueryData(['posts', id], newPost)
13 },
Sorted lists are for example pretty hard to update directly, as the position of entries could've potentially changed because of the update. Invalidating the whole list is the "safer" approach.

More about useMutation

  1. Using callbacks properly to avoid callbacks not firing
You can have callbacks on useMutation as well as on mutate itself but callbacks on useMutation fires before the callbacks on mutate.


const useUpdateTodo = () => useMutation({ mutationFn: updateTodo, // This is the callback on useMutation onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['todos', 'list'] }) }, }) // in the component const updateTodo = useUpdateTodo() updateTodo.mutate( { title: 'newTitle' }, // when the mutation finishes, // THIS MIGHT NOT GET FIRED IF COMPONENT IS UNMOUNTED BEFORE MUTATION FINISHES { onSuccess: () => history.push('/todos') } )
Good practices (recommendation by
  • Do things that are absolutely necessary and logic related (like query invalidation) in the useMutation callbacks.
  • Do UI related things like redirects or showing toast notifications in mutate callbacks. If the user navigated away from the current screen before the mutation finished, those will purposefully not fire.