Attributes : ✅ Recommended • 🔧 Fixable
Overview
The QueryClient contains the QueryCache, so you’d only want to create one instance of the QueryClient for the lifecycle of your application - not a new instance on every render.
Creating a new QueryClient on every render will:
Reset your entire cache
Cause all queries to refetch
Break optimistic updates
Cause performance issues
Rule Details
This rule prevents creating a new QueryClient instance inside a component function body, which would cause it to be recreated on every render.
Examples
Incorrect Code
Component Body
Multiple Instances
/* eslint "@tanstack/query/stable-query-client": "error" */
function App () {
// ❌ New QueryClient created on every render
const queryClient = new QueryClient ()
return (
< QueryClientProvider client = { queryClient } >
< Home />
</ QueryClientProvider >
)
}
Correct Code
Module-Level Creation
useState Initialization
useMemo
useRef
// ✅ QueryClient created once at module level
const queryClient = new QueryClient ()
function App () {
return (
< QueryClientProvider client = { queryClient } >
< Home />
</ QueryClientProvider >
)
}
Exception: Server Components
It’s allowed to create a new QueryClient inside an async Server Component, because the async function is only called once on the server.
Server Component (Next.js App Router)
// ✅ OK in Server Components
export default async function ServerComponent () {
const queryClient = new QueryClient ()
await queryClient . prefetchQuery ({
queryKey: [ 'posts' ],
queryFn: fetchPosts ,
})
return (
< HydrationBoundary state = { dehydrate ( queryClient )} >
< Posts />
</ HydrationBoundary >
)
}
The rule understands async function contexts and won’t flag this pattern.
Common Patterns
App-Level Provider
The most common pattern is creating the QueryClient at the module level:
import { QueryClient , QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient ({
defaultOptions: {
queries: {
staleTime: 60 * 1000 , // 1 minute
gcTime: 5 * 60 * 1000 , // 5 minutes
},
},
})
function App () {
return (
< QueryClientProvider client = { queryClient } >
< YourApp />
</ QueryClientProvider >
)
}
Component-Level Provider
Sometimes you need a provider lower in the tree. Use useState:
function NestedProvider ({ children } : { children : React . ReactNode }) {
const [ queryClient ] = useState (
() =>
new QueryClient ({
defaultOptions: {
queries: {
staleTime: Infinity ,
},
},
}),
)
return (
< QueryClientProvider client = { queryClient } >
{ children }
</ QueryClientProvider >
)
}
Testing Setup
In tests, create a new client for each test:
import { QueryClient , QueryClientProvider } from '@tanstack/react-query'
import { render } from '@testing-library/react'
export function createTestQueryClient () {
return new QueryClient ({
defaultOptions: {
queries: {
retry: false ,
},
mutations: {
retry: false ,
},
},
logger: {
log: console . log ,
warn: console . warn ,
error : () => {}, // Suppress errors in tests
},
})
}
export function renderWithClient ( ui : React . ReactElement ) {
const testQueryClient = createTestQueryClient ()
const { rerender , ... result } = render (
< QueryClientProvider client = { testQueryClient } >
{ ui }
</ QueryClientProvider > ,
)
return {
... result ,
rerender : ( rerenderUi : React . ReactElement ) =>
rerender (
< QueryClientProvider client = { testQueryClient } >
{ rerenderUi }
</ QueryClientProvider > ,
),
}
}
Why useState Over useMemo?
useState is more reliable
useState with a function initializer is the recommended approach because:
Guaranteed single execution : The initializer function runs exactly once
No dependencies : Unlike useMemo, you can’t accidentally provide wrong dependencies
React concurrent mode safe : Works correctly with React 18+ concurrent features
Clearer intent : Signals that this value should never change
// ✅ Best: useState with initializer
const [ queryClient ] = useState (() => new QueryClient ())
// ⚠️ Works but less ideal: useMemo
const queryClient = useMemo (() => new QueryClient (), [])
Auto-Fix
This rule includes an auto-fixer that will wrap your QueryClient creation in useState:
Before:
function App () {
const queryClient = new QueryClient ()
// ...
}
After:
function App () {
const [ queryClient ] = useState (() => new QueryClient ())
// ...
}
Review auto-fixes to ensure they’re appropriate for your use case.
Multiple QueryClients
Sometimes you need multiple QueryClient instances (e.g., for isolated contexts):
// ✅ Multiple stable instances
const appQueryClient = new QueryClient ()
const adminQueryClient = new QueryClient ({
defaultOptions: {
queries: { staleTime: 0 },
},
})
function App () {
return (
< QueryClientProvider client = { appQueryClient } >
< AppContent />
< QueryClientProvider client = { adminQueryClient } >
< AdminPanel />
</ QueryClientProvider >
</ QueryClientProvider >
)
}
Framework-Specific Patterns
Next.js App Router
'use client'
import { QueryClient , QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
export function Providers ({ children } : { children : React . ReactNode }) {
const [ queryClient ] = useState (() => new QueryClient ())
return (
< QueryClientProvider client = { queryClient } >
{ children }
</ QueryClientProvider >
)
}
Remix
import { QueryClient , QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
export default function App () {
const [ queryClient ] = useState (() => new QueryClient ())
return (
< html >
< body >
< QueryClientProvider client = { queryClient } >
< Outlet />
</ QueryClientProvider >
</ body >
</ html >
)
}
Debugging
If you suspect your QueryClient is being recreated:
const [ queryClient ] = useState (() => {
console . log ( 'Creating QueryClient' )
return new QueryClient ()
})
// This should only log once per component mount
Further Reading
QueryClient Learn about QueryClient configuration options
Important Defaults Understand TanStack Query’s default behaviors