React Query (TanStack Query) Guide
react
react-query
tanstack-query
state management
data fetching
Installation
npm install @tanstack/react-query
# or
yarn add @tanstack/react-query
Setup
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient()
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* Your app components */}
</QueryClientProvider>
)
}
Basic Query
import { useQuery } from '@tanstack/react-query'
function Example() {
const { isLoading, error, data } = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/api/todos').then(res => res.json()),
})
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
Query with Variables
function Todo({ todoId }) {
const { isLoading, error, data } = useQuery({
queryKey: ['todo', todoId],
queryFn: () => fetchTodoById(todoId),
})
// ... render todo
}
Mutation
import { useMutation, useQueryClient } from '@tanstack/react-query'
function AddTodo() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: newTodo => {
return fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo),
})
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
return (
<form onSubmit={(e) => {
e.preventDefault()
mutation.mutate({ title: 'New Todo' })
}}>
<button type="submit">Add Todo</button>
</form>
)
}
Query Invalidation
const queryClient = useQueryClient()
queryClient.invalidateQueries({ queryKey: ['todos'] })
Prefetching
const prefetchTodos = async () => {
await queryClient.prefetchQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
})
}
Infinite Queries
import { useInfiniteQuery } from '@tanstack/react-query'
function InfiniteTodos() {
const {
data,
error,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage,
status,
} = useInfiniteQuery({
queryKey: ['projects'],
queryFn: ({ pageParam = 0 }) => fetchProjects(pageParam),
getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
})
return status === 'loading' ? (
<p>Loading...</p>
) : status === 'error' ? (
<p>Error: {error.message}</p>
) : (
<>
{data.pages.map((group, i) => (
<React.Fragment key={i}>
{group.projects.map(project => (
<p key={project.id}>{project.name}</p>
))}
</React.Fragment>
))}
<div>
<button
onClick={() => fetchNextPage()}
disabled={!hasNextPage || isFetchingNextPage}
>
{isFetchingNextPage
? 'Loading more...'
: hasNextPage
? 'Load More'
: 'Nothing more to load'}
</button>
</div>
<div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
</>
)
}
Optimistic Updates
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async newTodo => {
await queryClient.cancelQueries({ queryKey: ['todos', newTodo.id] })
const previousTodo = queryClient.getQueryData(['todos', newTodo.id])
queryClient.setQueryData(['todos', newTodo.id], newTodo)
return { previousTodo }
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos', newTodo.id], context.previousTodo)
},
onSettled: newTodo => {
queryClient.invalidateQueries({ queryKey: ['todos', newTodo.id] })
},
})
Query Retries
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
retry: 3, // Will retry failed requests 3 times before displaying an error
})
Dependent Queries
const { data: user } = useQuery({
queryKey: ['user', email],
queryFn: () => fetchUser(email),
})
const { data: projects } = useQuery({
queryKey: ['projects', user?.id],
queryFn: () => fetchProjects(user.id),
enabled: !!user?.id,
})
Remember to always refer to the official React Query documentation for the most up-to-date information and best practices, as the library is actively developed and may introduce new features or changes.