Installation
yarn add @tanstack/react-query @tanstack/react-query-devtools
Configuration
// App.tsx import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const queryClient = new QueryClient(); function App() { return ( <QueryClientProvider client={queryClient}> ... </QueryClientProvider> ) }
React Query DevTool
By default React Query Devtools are only included in bundles when
process.env.NODE_ENV === "development"
And to activate the devtools, we simply import and add:
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tankstack/react-query-devtools" const queryClient = new QueryClient(); function App() { return ( <QueryClientProvider client={queryClient}> ... <ReactQueryDevtools /> </ QueryClientProvider> ) }
useQuery
Basic Example for Queries
const getData = async () => { const res = await apiClient.get("..."); // do data transformation here const transformedData = ... return transformedData } const { data, isError, isLoading } = useQuery({ queryKey: ["my-list", <some-id>], queryFn: getData, staleTime: 30* 1000, gcTime: 5 * 60 * 1000 })
isLoading
and isFetching
isLoading
and isFetching
Here isLoading
means the following happen:
- No Cahched Data (i.e., all data have been staled)
isFetching
Therefore (think of events of one data as a sample space)
where
isFetching
:staleTime
exceeded andnoCachedData
:gcTime
exceeded
stateTime
vs gcTime
stateTime
vs gcTime
stateTime
This is for when data needs to be refetchedgcTime
This is for how long to keep data that might be reused later (like offline / before fetching new data)- query goes into cold storage if there is no active
useQuery
- cache data expires after
gcTime
(default:5mins
), data is not available any more
- query goes into cold storage if there is no active
We say that a data is cached
until gcTime
is not reached.
prefetchQuery
For better UX sometimes we prefetch data before user nevigating to that page:
export default function SomeContent () { const queryClient = useQueryClient(); const [currentPage, setCurrentPage] = useState(1); useEffect(() => { const nextPage = currentPage + 1; queryClient.prefetchQuery({ queryKey: ["posts", nextPage]}) }, [currnetPage]); const { data } = useQuery({ queryKey: ["post", currentPage], ... }); }
useMutation
Basic Example for Mutations
1const deleteMutation = useMutation({ 2 mutationFn: (postId: string) => deletePost(postId) 3}); 4 5const { isSuccess, isPending } = deleteMutation; 6 7const deletePost = (postId: string) => { 8 deleteMutation.mutate(postId); 9} 10 11const selectSomething = (someId: string) => { 12 deleteMutation.reset() 13 ... 14}
Reset Mutations
- Note that after mutation the booleans
isSuccess
andisPending
will be persistent unless we manually reset it - This could be a problem because when we try to select something new, we don't wish the failed / loading message remaining there
- So in line 12 above we must reset it
Optimistic Update
import { useMutation, useQueryClient } from '@tanstack/react-query' function useUpdateTodo() { const queryClient = useQueryClient() return useMutation({ mutationFn: updateTodo, // your API call onMutate: async (newTodo) => { // cancel any outgoing refetches to avoid overwriting optimistic update await queryClient.cancelQueries({ queryKey: ['todos'] }) const previousTodos = queryClient.getQueryData(['todos']) queryClient.setQueryData(['todos'], (oldTodos) => { return oldTodos.map(todo => todo.id === newTodo.id ? newTodo : todo ) }) return { previousTodos } }, onError: (err, newTodo, context) => { queryClient.setQueryData(['todos'], context.previousTodos) }, onSettled: () => { // always refetch after error or success queryClient.invalidateQueries({ queryKey: ['todos'] }) }, }) }
Cache Invalidations
export default function SomeComponent() { const queryClient = useQueryClient(); // invalidate exact query queryClient.invalidateQueries({ queryKey: ['todos', 1] }); // invalidate multiple queries using prefix queryClient.invalidateQueries({ queryKey: ['todos'], exact: false }); }
exact
by default isfalse
. That means it automatically invalidates all caches by prefix- by default it only marks the data as
stale
(notgcTime exceeded
) - it does not remove the data from cache