Skip to main content
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:
  1. Reset your entire cache
  2. Cause all queries to refetch
  3. Break optimistic updates
  4. 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

/* 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

// ✅ 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:
App.tsx
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:
NestedProvider.tsx
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:
test-utils.tsx
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 with a function initializer is the recommended approach because:
  1. Guaranteed single execution: The initializer function runs exactly once
  2. No dependencies: Unlike useMemo, you can’t accidentally provide wrong dependencies
  3. React concurrent mode safe: Works correctly with React 18+ concurrent features
  4. 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:
eslint --fix src/
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

app/providers.tsx
'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

app/root.tsx
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

Build docs developers (and LLMs) love