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.