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:
Uninstall Old Package
Install New Package
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
Update Package
npm uninstall react-query
npm install @tanstack/react-query @tanstack/react-query-devtools
Run Import Codemod
Update all import statements.
Run Key Transformation Codemod
Convert string keys to array keys.
Update Query Options
Wrap all options in objects if not already done.
Replace idle Checks
Use isInitialLoading or fetchStatus === 'idle'.
Update useQueries
Wrap query arrays in { queries: [...] }.
Move onSuccess to useEffect
Replace onSuccess callbacks with useEffect.
Update Persister Imports
Use new persister package names.
Test Offline Behavior
Verify queries behave correctly when offline.
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!