Skip to main content
v4 is a major version with breaking changes. This guide covers everything you need to know to migrate from v3.

Package Rename

react-query → @tanstack/react-query

The package has been renamed and moved to the TanStack organization:
npm uninstall react-query

Update Imports

import { useQuery } from 'react-query'
import { ReactQueryDevtools } from 'react-query/devtools'

Codemod Available

npx jscodeshift ./path/to/src/ \
  --extensions=js,jsx \
  --transform=./node_modules/@tanstack/react-query/codemods/v4/replace-import-specifier.js
The codemod only changes imports. You must install the new packages manually.

Breaking Changes

Query Keys Must Be Arrays

All query and mutation keys must be arrays:
useQuery('todos', fetchTodos)
useQuery(['todos', { status }], fetchTodos)

Codemod Available

npx jscodeshift ./path/to/src/ \
  --extensions=js,jsx \
  --transform=./node_modules/@tanstack/react-query/codemods/v4/key-transformation.js

idle State Removed

The idle state has been replaced with loading + fetchStatus: 'idle':
const { status } = useQuery(['todos'], fetchTodos, { enabled: false })

if (status === 'idle') {
  return <div>Not ready...</div>
}
status reflects the data state (loading/success/error), while fetchStatus reflects the fetch state (idle/fetching/paused). This provides better offline support and more granular control.

New useQueries API

useQueries([
  { queryKey: ['post', 1], queryFn: fetchPost },
  { queryKey: ['post', 2], queryFn: fetchPost },
])

undefined is Illegal for Successful Queries

Query functions cannot return undefined:
// Accidentally returns undefined
useQuery(['todos'], () =>
  axios.get('/todos').then((result) => console.log(result.data))
)
If your query function returns undefined, the query will error and the error will be logged in development.

Network Mode: Queries Need Network by Default

// Queries/mutations ran even offline
const { data } = useQuery(['todos'], fetchTodos)
Three network modes:
  • online (default): Requires network connection
  • offlineFirst: Like v3, always runs
  • always: Runs even when marked offline

notifyOnChangeProps Default Changed

// Default: re-render on any query change
useQuery(['todos'], fetchTodos)

// Opt-in to tracking
useQuery(['todos'], fetchTodos, {
  notifyOnChangeProps: 'tracked',
})

notifyOnChangePropsExclusions Removed

Now that tracking is default, this option is no longer needed.

cancelRefetch Now Defaults to true

// Multiple refetches allowed in parallel (last one wins)
queryClient.refetchQueries(['todos'])
queryClient.refetchQueries(['todos'])

Query Filters Unified

queryClient.invalidateQueries(['todos'], {
  active: true,
  inactive: false,
  refetchActive: true,
  refetchInactive: false,
})

onSuccess Not Called from setQueryData

useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  onSuccess: (data) => {
    console.log('This runs on fetch AND setQueryData')
  },
})

Persister Plugins Renamed

import { persistQueryClient } from 'react-query/persistQueryClient-experimental'
import { createWebStoragePersistor } from 'react-query/createWebStoragePersistor-experimental'

Promise cancel Method Removed

Use AbortController for query cancellation:
const promise = fetchTodos()
promise.cancel = () => {
  // custom cancellation
}

TypeScript 4.1+ Required

TypeScript 4.1 or higher is now required.

setLogger Moved to QueryClient

import { setLogger } from 'react-query'

setLogger(customLogger)
const queryClient = new QueryClient()

Server-Side: No Manual Garbage Collection

On the server, gcTime now defaults to Infinity instead of 5 minutes:
// In v4, server-side queries don't garbage collect by default
// The Node.js process clears everything when request completes

// To restore v3 behavior:
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      gcTime: 5 * 60 * 1000, // 5 minutes
    },
  },
})

No Logging in Production

Errors are no longer logged to console in production mode. They still appear in development.

Hydration Exports Consolidated

import { dehydrate, hydrate } from 'react-query/hydration'

src/react Renamed to src/reactjs

If you imported from react-query/react:
import { QueryClientProvider } from 'react-query/react'

New Features

React 18 Support

Full support for React 18 and concurrent features.

Proper Offline Support

New networkMode option provides fine-grained control:
useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  networkMode: 'online', // 'online' | 'offlineFirst' | 'always'
})

Tracked Queries by Default

Automatic render optimization:
const { data, isLoading } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
})

// Only re-renders when data or isLoading changes
// (because those are the only properties accessed)

Bail Out of setQueryData

queryClient.setQueryData(['todo', id], (previousTodo) =>
  previousTodo ? { ...previousTodo, done: true } : undefined
)

// Returning undefined prevents the update

Mutation Garbage Collection

Mutations now have gcTime (default 5 minutes):
const mutation = useMutation({
  mutationFn: addTodo,
  gcTime: 10 * 60 * 1000, // Keep for 10 minutes
})

Custom Contexts for Multiple Providers

const context = React.createContext<QueryClient | undefined>(undefined)
const queryClient = new QueryClient()

function App() {
  return (
    <QueryClientProvider client={queryClient} context={context}>
      <Component />
    </QueryClientProvider>
  )
}

function Component() {
  const { data } = useQuery(
    {
      queryKey: ['user'],
      queryFn: fetchUser,
      context, // Use specific context
    }
  )
}

Migration Checklist

1

Update Package

npm uninstall react-query
npm install @tanstack/react-query @tanstack/react-query-devtools
2

Run Import Codemod

Update all import statements.
3

Run Key Transformation Codemod

Convert string keys to array keys.
4

Update Query Options

Wrap all options in objects if not already done.
5

Replace idle Checks

Use isInitialLoading or fetchStatus === 'idle'.
6

Update useQueries

Wrap query arrays in { queries: [...] }.
7

Move onSuccess to useEffect

Replace onSuccess callbacks with useEffect.
8

Update Persister Imports

Use new persister package names.
9

Test Offline Behavior

Verify queries behave correctly when offline.
10

Test Everything

Thoroughly test all query and mutation functionality.
v4 brings significant improvements to offline support, TypeScript types, and render optimization. The migration effort is worth it!

Build docs developers (and LLMs) love