or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdcore-query-hooks.mdindex.mdmutations.mdproviders-context.mdsuspense-integration.mdutilities.md

providers-context.mddocs/

0

# Provider & Context System

1

2

Components and hooks for managing QueryClient context, hydration, and error boundaries.

3

4

## QueryClientProvider

5

6

**Provider component for QueryClient context**

7

8

```typescript { .api }

9

const QueryClientProvider: React.FC<QueryClientProviderProps>

10

11

interface QueryClientProviderProps {

12

client: QueryClient

13

children?: React.ReactNode

14

}

15

```

16

17

### Basic Setup

18

19

```typescript { .api }

20

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

21

22

// Create a client

23

const queryClient = new QueryClient({

24

defaultOptions: {

25

queries: {

26

staleTime: 5 * 60 * 1000, // 5 minutes

27

gcTime: 10 * 60 * 1000, // 10 minutes

28

retry: (failureCount, error) => {

29

if (error.status === 404) return false

30

return failureCount < 3

31

}

32

},

33

mutations: {

34

retry: 1

35

}

36

}

37

})

38

39

function App() {

40

return (

41

<QueryClientProvider client={queryClient}>

42

<div className="app">

43

<Router>

44

<Routes>

45

<Route path="/" element={<Home />} />

46

<Route path="/users/:id" element={<UserProfile />} />

47

</Routes>

48

</Router>

49

</div>

50

</QueryClientProvider>

51

)

52

}

53

```

54

55

### Advanced Configuration

56

57

```typescript { .api }

58

import { QueryClient, QueryClientProvider, MutationCache, QueryCache } from '@tanstack/react-query'

59

60

const queryClient = new QueryClient({

61

queryCache: new QueryCache({

62

onError: (error, query) => {

63

console.error(`Query failed:`, error)

64

// Global query error handling

65

if (error.status === 401) {

66

// Redirect to login

67

window.location.href = '/login'

68

}

69

},

70

onSuccess: (data, query) => {

71

// Global success handling

72

console.log(`Query succeeded for key:`, query.queryKey)

73

}

74

}),

75

mutationCache: new MutationCache({

76

onError: (error, variables, context, mutation) => {

77

console.error(`Mutation failed:`, error)

78

// Show global error notification

79

toast.error(`Operation failed: ${error.message}`)

80

},

81

onSuccess: (data, variables, context, mutation) => {

82

// Show global success notification

83

if (mutation.options.meta?.successMessage) {

84

toast.success(mutation.options.meta.successMessage)

85

}

86

}

87

}),

88

defaultOptions: {

89

queries: {

90

staleTime: 5 * 60 * 1000,

91

gcTime: 10 * 60 * 1000,

92

refetchOnWindowFocus: false,

93

retry: (failureCount, error) => {

94

// Custom retry logic

95

if (error.status === 404 || error.status === 403) return false

96

return failureCount < 3

97

}

98

},

99

mutations: {

100

retry: (failureCount, error) => {

101

if (error.status >= 400 && error.status < 500) return false

102

return failureCount < 2

103

}

104

}

105

}

106

})

107

108

function App() {

109

return (

110

<QueryClientProvider client={queryClient}>

111

<ErrorBoundary>

112

<Suspense fallback={<GlobalLoadingSpinner />}>

113

<AppContent />

114

</Suspense>

115

</ErrorBoundary>

116

</QueryClientProvider>

117

)

118

}

119

```

120

121

## QueryClientContext

122

123

**React context that holds the QueryClient instance**

124

125

```typescript { .api }

126

const QueryClientContext: React.Context<QueryClient | undefined>

127

```

128

129

### Direct Context Usage

130

131

```typescript { .api }

132

import { useContext } from 'react'

133

import { QueryClientContext } from '@tanstack/react-query'

134

135

function MyComponent() {

136

const queryClient = useContext(QueryClientContext)

137

138

if (!queryClient) {

139

throw new Error('QueryClient not found. Make sure you are using QueryClientProvider.')

140

}

141

142

const handleInvalidate = () => {

143

queryClient.invalidateQueries({ queryKey: ['posts'] })

144

}

145

146

return (

147

<button onClick={handleInvalidate}>

148

Refresh Posts

149

</button>

150

)

151

}

152

```

153

154

## useQueryClient

155

156

**Hook to access the current QueryClient instance**

157

158

```typescript { .api }

159

function useQueryClient(queryClient?: QueryClient): QueryClient

160

```

161

162

### Basic Usage

163

164

```typescript { .api }

165

import { useQueryClient } from '@tanstack/react-query'

166

167

function RefreshButton() {

168

const queryClient = useQueryClient()

169

170

const handleRefreshAll = () => {

171

// Invalidate all queries

172

queryClient.invalidateQueries()

173

}

174

175

const handleRefreshPosts = () => {

176

// Invalidate specific queries

177

queryClient.invalidateQueries({ queryKey: ['posts'] })

178

}

179

180

return (

181

<div>

182

<button onClick={handleRefreshPosts}>Refresh Posts</button>

183

<button onClick={handleRefreshAll}>Refresh All</button>

184

</div>

185

)

186

}

187

```

188

189

### Cache Manipulation

190

191

```typescript { .api }

192

function PostActions({ postId }: { postId: number }) {

193

const queryClient = useQueryClient()

194

195

const handleOptimisticUpdate = () => {

196

queryClient.setQueryData(['post', postId], (oldPost: Post) => ({

197

...oldPost,

198

likes: oldPost.likes + 1

199

}))

200

}

201

202

const handlePrefetch = () => {

203

queryClient.prefetchQuery({

204

queryKey: ['post-comments', postId],

205

queryFn: () => fetchPostComments(postId),

206

staleTime: 5 * 60 * 1000

207

})

208

}

209

210

const handleRemoveFromCache = () => {

211

queryClient.removeQueries({ queryKey: ['post', postId] })

212

}

213

214

return (

215

<div>

216

<button onClick={handleOptimisticUpdate}>Like</button>

217

<button onClick={handlePrefetch}>Prefetch Comments</button>

218

<button onClick={handleRemoveFromCache}>Remove from Cache</button>

219

</div>

220

)

221

}

222

```

223

224

### Manual Cache Updates

225

226

```typescript { .api }

227

function usePostCache() {

228

const queryClient = useQueryClient()

229

230

const updatePost = (postId: number, updates: Partial<Post>) => {

231

queryClient.setQueryData(['post', postId], (oldPost: Post) => ({

232

...oldPost,

233

...updates

234

}))

235

}

236

237

const addPost = (newPost: Post) => {

238

// Update posts list

239

queryClient.setQueryData(['posts'], (oldPosts: Post[]) =>

240

[newPost, ...oldPosts]

241

)

242

243

// Set individual post data

244

queryClient.setQueryData(['post', newPost.id], newPost)

245

}

246

247

const removePost = (postId: number) => {

248

// Remove from posts list

249

queryClient.setQueryData(['posts'], (oldPosts: Post[]) =>

250

oldPosts.filter(post => post.id !== postId)

251

)

252

253

// Remove individual post data

254

queryClient.removeQueries({ queryKey: ['post', postId] })

255

}

256

257

return { updatePost, addPost, removePost }

258

}

259

```

260

261

## HydrationBoundary

262

263

**Component for hydrating server-side rendered queries on the client**

264

265

```typescript { .api }

266

const HydrationBoundary: React.FC<HydrationBoundaryProps>

267

268

interface HydrationBoundaryProps {

269

state: DehydratedState | null | undefined

270

options?: HydrateOptions

271

children?: React.ReactNode

272

queryClient?: QueryClient

273

}

274

```

275

276

### Basic SSR Setup

277

278

```typescript { .api }

279

// Server-side (Next.js example)

280

import { QueryClient, dehydrate } from '@tanstack/react-query'

281

282

export async function getServerSideProps() {

283

const queryClient = new QueryClient()

284

285

// Prefetch data on the server

286

await queryClient.prefetchQuery({

287

queryKey: ['posts'],

288

queryFn: fetchPosts

289

})

290

291

await queryClient.prefetchQuery({

292

queryKey: ['user', 'me'],

293

queryFn: fetchCurrentUser

294

})

295

296

return {

297

props: {

298

dehydratedState: dehydrate(queryClient)

299

}

300

}

301

}

302

303

// Client-side

304

function PostsPage({ dehydratedState }: { dehydratedState: DehydratedState }) {

305

return (

306

<HydrationBoundary state={dehydratedState}>

307

<PostsList />

308

<UserProfile />

309

</HydrationBoundary>

310

)

311

}

312

```

313

314

### Advanced Hydration

315

316

```typescript { .api }

317

function App({ dehydratedState }: { dehydratedState: DehydratedState }) {

318

const [queryClient] = useState(() => new QueryClient({

319

defaultOptions: {

320

queries: {

321

staleTime: 60 * 1000 // 1 minute

322

}

323

}

324

}))

325

326

return (

327

<QueryClientProvider client={queryClient}>

328

<HydrationBoundary

329

state={dehydratedState}

330

options={{

331

defaultOptions: {

332

queries: {

333

staleTime: 5 * 60 * 1000 // Override stale time for hydrated queries

334

}

335

}

336

}}

337

>

338

<Router>

339

<Routes>

340

<Route path="/" element={<HomePage />} />

341

<Route path="/posts" element={<PostsPage />} />

342

</Routes>

343

</Router>

344

</HydrationBoundary>

345

</QueryClientProvider>

346

)

347

}

348

```

349

350

### Selective Hydration

351

352

```typescript { .api }

353

// Server-side with selective dehydration

354

export async function getServerSideProps() {

355

const queryClient = new QueryClient()

356

357

await queryClient.prefetchQuery({

358

queryKey: ['posts'],

359

queryFn: fetchPosts

360

})

361

362

await queryClient.prefetchQuery({

363

queryKey: ['user-settings'],

364

queryFn: fetchUserSettings

365

})

366

367

return {

368

props: {

369

dehydratedState: dehydrate(queryClient, {

370

shouldDehydrateQuery: (query) => {

371

// Only dehydrate posts, not user settings for security

372

return query.queryKey[0] === 'posts'

373

}

374

})

375

}

376

}

377

}

378

```

379

380

### Nested Hydration Boundaries

381

382

```typescript { .api }

383

function Layout({ globalDehydratedState, children }: {

384

globalDehydratedState: DehydratedState

385

children: React.ReactNode

386

}) {

387

return (

388

<HydrationBoundary state={globalDehydratedState}>

389

<Header />

390

<main>{children}</main>

391

<Footer />

392

</HydrationBoundary>

393

)

394

}

395

396

function PostPage({ pageDehydratedState }: { pageDehydratedState: DehydratedState }) {

397

return (

398

<HydrationBoundary state={pageDehydratedState}>

399

<PostContent />

400

<PostComments />

401

</HydrationBoundary>

402

)

403

}

404

```

405

406

## QueryErrorResetBoundary

407

408

**Component for providing error reset functionality to child queries**

409

410

```typescript { .api }

411

const QueryErrorResetBoundary: React.FC<QueryErrorResetBoundaryProps>

412

413

interface QueryErrorResetBoundaryProps {

414

children: QueryErrorResetBoundaryFunction | React.ReactNode

415

}

416

417

type QueryErrorResetBoundaryFunction = (

418

value: QueryErrorResetBoundaryValue,

419

) => React.ReactNode

420

421

interface QueryErrorResetBoundaryValue {

422

clearReset: QueryErrorClearResetFunction

423

isReset: QueryErrorIsResetFunction

424

reset: QueryErrorResetFunction

425

}

426

```

427

428

### Basic Error Boundary

429

430

```typescript { .api }

431

import { QueryErrorResetBoundary } from '@tanstack/react-query'

432

import { ErrorBoundary } from 'react-error-boundary'

433

434

function App() {

435

return (

436

<QueryErrorResetBoundary>

437

{({ reset }) => (

438

<ErrorBoundary

439

onReset={reset}

440

fallbackRender={({ error, resetErrorBoundary }) => (

441

<div className="error-fallback">

442

<h2>Something went wrong:</h2>

443

<pre className="error-message">{error.message}</pre>

444

<button onClick={resetErrorBoundary}>

445

Try again

446

</button>

447

</div>

448

)}

449

>

450

<Suspense fallback={<div>Loading...</div>}>

451

<UserDashboard />

452

</Suspense>

453

</ErrorBoundary>

454

)}

455

</QueryErrorResetBoundary>

456

)

457

}

458

```

459

460

### useQueryErrorResetBoundary

461

462

```typescript { .api }

463

function useQueryErrorResetBoundary(): QueryErrorResetBoundaryValue

464

```

465

466

### Custom Error Reset Logic

467

468

```typescript { .api }

469

import { useQueryErrorResetBoundary } from '@tanstack/react-query'

470

471

function CustomErrorBoundary({ children }: { children: React.ReactNode }) {

472

const { reset, isReset } = useQueryErrorResetBoundary()

473

const [hasError, setHasError] = useState(false)

474

475

useEffect(() => {

476

if (isReset()) {

477

setHasError(false)

478

}

479

}, [isReset])

480

481

if (hasError) {

482

return (

483

<div className="error-boundary">

484

<h2>Oops! Something went wrong</h2>

485

<button

486

onClick={() => {

487

reset()

488

setHasError(false)

489

}}

490

>

491

Reset and try again

492

</button>

493

</div>

494

)

495

}

496

497

return (

498

<ErrorBoundary

499

onError={() => setHasError(true)}

500

fallback={null}

501

>

502

{children}

503

</ErrorBoundary>

504

)

505

}

506

```

507

508

### Nested Error Boundaries

509

510

```typescript { .api }

511

function Layout() {

512

return (

513

<QueryErrorResetBoundary>

514

{({ reset }) => (

515

<div>

516

<ErrorBoundary

517

onReset={reset}

518

fallbackRender={({ resetErrorBoundary }) => (

519

<div className="header-error">

520

<span>Header failed to load</span>

521

<button onClick={resetErrorBoundary}>Retry</button>

522

</div>

523

)}

524

>

525

<Header />

526

</ErrorBoundary>

527

528

<main>

529

<ErrorBoundary

530

onReset={reset}

531

fallbackRender={({ resetErrorBoundary }) => (

532

<div className="content-error">

533

<h2>Content failed to load</h2>

534

<button onClick={resetErrorBoundary}>Retry</button>

535

</div>

536

)}

537

>

538

<MainContent />

539

</ErrorBoundary>

540

</main>

541

</div>

542

)}

543

</QueryErrorResetBoundary>

544

)

545

}

546

```

547

548

## IsRestoringProvider

549

550

**Provider component for tracking restoration state**

551

552

```typescript { .api }

553

const IsRestoringProvider: React.Provider<boolean>

554

```

555

556

### useIsRestoring

557

558

```typescript { .api }

559

function useIsRestoring(): boolean

560

```

561

562

### Usage

563

564

```typescript { .api }

565

import { useIsRestoring } from '@tanstack/react-query'

566

567

function GlobalLoadingIndicator() {

568

const isRestoring = useIsRestoring()

569

const isFetching = useIsFetching()

570

571

if (isRestoring) {

572

return (

573

<div className="restoration-indicator">

574

Restoring queries from server...

575

</div>

576

)

577

}

578

579

if (isFetching > 0) {

580

return (

581

<div className="fetching-indicator">

582

Loading... ({isFetching} queries)

583

</div>

584

)

585

}

586

587

return null

588

}

589

```

590

591

### Custom Restoration Handling

592

593

```typescript { .api }

594

function App({ dehydratedState }: { dehydratedState: DehydratedState }) {

595

const [queryClient] = useState(() => new QueryClient())

596

597

return (

598

<QueryClientProvider client={queryClient}>

599

<HydrationBoundary state={dehydratedState}>

600

<IsRestoringProvider value={true}>

601

<RestorationAwareApp />

602

</IsRestoringProvider>

603

</HydrationBoundary>

604

</QueryClientProvider>

605

)

606

}

607

608

function RestorationAwareApp() {

609

const isRestoring = useIsRestoring()

610

611

// Don't render interactive elements during restoration

612

if (isRestoring) {

613

return <SkeletonLoader />

614

}

615

616

return <InteractiveApp />

617

}

618

```

619

620

## Core Management Classes

621

622

The following core classes are available for advanced use cases and custom implementations:

623

624

### QueryCache

625

626

**Manages the cache of queries and their states**

627

628

```typescript { .api }

629

class QueryCache {

630

constructor(config?: QueryCacheConfig)

631

632

add(query: Query): void

633

remove(query: Query): void

634

find(filters: QueryFilters): Query | undefined

635

findAll(filters?: QueryFilters): Query[]

636

notify(event: QueryCacheNotifyEvent): void

637

subscribe(callback: (event: QueryCacheNotifyEvent) => void): () => void

638

clear(): void

639

}

640

641

interface QueryCacheConfig {

642

onError?: (error: unknown, query: Query) => void

643

onSuccess?: (data: unknown, query: Query) => void

644

onSettled?: (data: unknown | undefined, error: unknown | null, query: Query) => void

645

}

646

```

647

648

**Example:**

649

```typescript

650

import { QueryCache } from '@tanstack/react-query'

651

652

const queryCache = new QueryCache({

653

onError: (error, query) => {

654

console.error('Query failed:', query.queryKey, error)

655

},

656

onSuccess: (data, query) => {

657

console.log('Query succeeded:', query.queryKey)

658

}

659

})

660

661

// Use in QueryClient

662

const queryClient = new QueryClient({ queryCache })

663

```

664

665

### MutationCache

666

667

**Manages the cache of mutations and their states**

668

669

```typescript { .api }

670

class MutationCache {

671

constructor(config?: MutationCacheConfig)

672

673

add(mutation: Mutation): void

674

remove(mutation: Mutation): void

675

find(filters: MutationFilters): Mutation | undefined

676

findAll(filters?: MutationFilters): Mutation[]

677

notify(event: MutationCacheNotifyEvent): void

678

subscribe(callback: (event: MutationCacheNotifyEvent) => void): () => void

679

clear(): void

680

}

681

682

interface MutationCacheConfig {

683

onError?: (error: unknown, variables: unknown, context: unknown, mutation: Mutation) => void

684

onSuccess?: (data: unknown, variables: unknown, context: unknown, mutation: Mutation) => void

685

onSettled?: (data: unknown | undefined, error: unknown | null, variables: unknown, context: unknown, mutation: Mutation) => void

686

}

687

```

688

689

### Core Managers

690

691

#### focusManager

692

693

**Manages window focus detection for automatic refetching**

694

695

```typescript { .api }

696

const focusManager: {

697

subscribe(callback: (focused: boolean) => void): () => void

698

setFocused(focused?: boolean): void

699

isFocused(): boolean

700

setEventListener(handleFocus: () => void): void

701

}

702

```

703

704

**Example:**

705

```typescript

706

import { focusManager } from '@tanstack/react-query'

707

708

// Custom focus management

709

focusManager.setEventListener(() => {

710

// Custom logic to determine if window is focused

711

const hasFocus = document.hasFocus()

712

focusManager.setFocused(hasFocus)

713

})

714

715

// Subscribe to focus changes

716

const unsubscribe = focusManager.subscribe((focused) => {

717

console.log('Window focus changed:', focused)

718

})

719

```

720

721

#### onlineManager

722

723

**Manages network connectivity detection for automatic refetching**

724

725

```typescript { .api }

726

const onlineManager: {

727

subscribe(callback: (online: boolean) => void): () => void

728

setOnline(online?: boolean): void

729

isOnline(): boolean

730

setEventListener(handleOnline: () => void): void

731

}

732

```

733

734

**Example:**

735

```typescript

736

import { onlineManager } from '@tanstack/react-query'

737

738

// Custom online detection

739

onlineManager.setEventListener(() => {

740

onlineManager.setOnline(navigator.onLine)

741

})

742

743

// Subscribe to online status changes

744

const unsubscribe = onlineManager.subscribe((online) => {

745

if (online) {

746

console.log('Connection restored')

747

} else {

748

console.log('Connection lost')

749

}

750

})

751

```

752

753

#### notifyManager

754

755

**Manages batching and scheduling of notifications**

756

757

```typescript { .api }

758

const notifyManager: {

759

schedule(fn: () => void): void

760

batchCalls<T extends Array<unknown>>(fn: (...args: T) => void): (...args: T) => void

761

flush(): void

762

}

763

```

764

765

### Observer Classes

766

767

Advanced observer classes for custom query management:

768

769

#### QueryObserver

770

771

**Lower-level observer for individual queries**

772

773

```typescript { .api }

774

class QueryObserver<TQueryFnData, TError, TData, TQueryKey extends QueryKey> {

775

constructor(client: QueryClient, options: QueryObserverOptions<TQueryFnData, TError, TData, TQueryKey>)

776

777

subscribe(listener?: (result: QueryObserverResult<TData, TError>) => void): () => void

778

getCurrentResult(): QueryObserverResult<TData, TError>

779

trackResult(result: QueryObserverResult<TData, TError>): QueryObserverResult<TData, TError>

780

getOptimisticResult(options: QueryObserverOptions<TQueryFnData, TError, TData, TQueryKey>): QueryObserverResult<TData, TError>

781

updateResult(): void

782

setOptions(options: QueryObserverOptions<TQueryFnData, TError, TData, TQueryKey>): void

783

destroy(): void

784

}

785

```

786

787

#### InfiniteQueryObserver

788

789

**Observer for infinite/paginated queries**

790

791

```typescript { .api }

792

class InfiniteQueryObserver<TQueryFnData, TError, TData, TQueryKey extends QueryKey, TPageParam> {

793

constructor(client: QueryClient, options: InfiniteQueryObserverOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>)

794

795

subscribe(listener?: (result: InfiniteQueryObserverResult<TData, TError>) => void): () => void

796

getCurrentResult(): InfiniteQueryObserverResult<TData, TError>

797

// ... similar methods to QueryObserver

798

}

799

```

800

801

#### MutationObserver

802

803

**Observer for individual mutations**

804

805

```typescript { .api }

806

class MutationObserver<TData, TError, TVariables, TContext> {

807

constructor(client: QueryClient, options: MutationObserverOptions<TData, TError, TVariables, TContext>)

808

809

subscribe(listener?: (result: MutationObserverResult<TData, TError, TVariables, TContext>) => void): () => void

810

getCurrentResult(): MutationObserverResult<TData, TError, TVariables, TContext>

811

mutate(variables: TVariables, options?: MutateOptions<TData, TError, TVariables, TContext>): Promise<TData>

812

reset(): void

813

destroy(): void

814

}

815

```

816

817

### Hydration Utilities

818

819

#### dehydrate

820

821

**Serializes QueryClient state for SSR**

822

823

```typescript { .api }

824

function dehydrate(

825

client: QueryClient,

826

options?: DehydrateOptions

827

): DehydratedState

828

829

interface DehydrateOptions {

830

shouldDehydrateMutation?: (mutation: Mutation) => boolean

831

shouldDehydrateQuery?: (query: Query) => boolean

832

}

833

```

834

835

#### hydrate

836

837

**Restores QueryClient state from serialized data**

838

839

```typescript { .api }

840

function hydrate(

841

client: QueryClient,

842

dehydratedState: DehydratedState,

843

options?: HydrateOptions

844

): void

845

846

interface HydrateOptions {

847

defaultOptions?: DefaultOptions

848

}

849

```

850

851

#### defaultShouldDehydrateQuery

852

853

**Default filter for query dehydration**

854

855

```typescript { .api }

856

function defaultShouldDehydrateQuery(query: Query): boolean

857

```

858

859

#### defaultShouldDehydrateMutation

860

861

**Default filter for mutation dehydration**

862

863

```typescript { .api }

864

function defaultShouldDehydrateMutation(mutation: Mutation): boolean

865

```

866

867

**Example:**

868

```typescript

869

import {

870

dehydrate,

871

hydrate,

872

defaultShouldDehydrateQuery,

873

defaultShouldDehydrateMutation

874

} from '@tanstack/react-query'

875

876

// Custom dehydration with selective queries

877

export async function getServerSideProps() {

878

const queryClient = new QueryClient()

879

880

// Prefetch data

881

await queryClient.prefetchQuery({

882

queryKey: ['posts'],

883

queryFn: fetchPosts

884

})

885

886

return {

887

props: {

888

dehydratedState: dehydrate(queryClient, {

889

shouldDehydrateQuery: (query) => {

890

// Use default logic plus custom conditions

891

return defaultShouldDehydrateQuery(query) &&

892

!query.queryKey.includes('sensitive')

893

}

894

})

895

}

896

}

897

}

898

```

899

900

## Best Practices

901

902

### QueryClient Singleton

903

904

```typescript { .api }

905

// ❌ Don't create a new QueryClient on every render

906

function App() {

907

return (

908

<QueryClientProvider client={new QueryClient()}>

909

{/* This creates a new client on every render */}

910

</QueryClientProvider>

911

)

912

}

913

914

// ✅ Create QueryClient outside component or use useState

915

const queryClient = new QueryClient()

916

917

function App() {

918

return (

919

<QueryClientProvider client={queryClient}>

920

{/* QueryClient is stable across renders */}

921

</QueryClientProvider>

922

)

923

}

924

925

// ✅ Or use useState for client-side apps

926

function App() {

927

const [queryClient] = useState(() => new QueryClient())

928

929

return (

930

<QueryClientProvider client={queryClient}>

931

{/* QueryClient is created once and stable */}

932

</QueryClientProvider>

933

)

934

}

935

```

936

937

### Error Boundary Hierarchy

938

939

```typescript { .api }

940

function App() {

941

return (

942

<QueryClientProvider client={queryClient}>

943

{/* Global error boundary for unrecoverable errors */}

944

<QueryErrorResetBoundary>

945

{({ reset }) => (

946

<ErrorBoundary

947

onReset={reset}

948

fallbackRender={GlobalErrorFallback}

949

>

950

<Router>

951

<Routes>

952

<Route path="/*" element={

953

/* Page-level error boundaries for recoverable errors */

954

<QueryErrorResetBoundary>

955

{({ reset }) => (

956

<ErrorBoundary

957

onReset={reset}

958

fallbackRender={PageErrorFallback}

959

>

960

<PageContent />

961

</ErrorBoundary>

962

)}

963

</QueryErrorResetBoundary>

964

} />

965

</Routes>

966

</Router>

967

</ErrorBoundary>

968

)}

969

</QueryErrorResetBoundary>

970

</QueryClientProvider>

971

)

972

}

973

```

974

975

### Context Optimization

976

977

```typescript { .api }

978

// ✅ Use the useQueryClient hook instead of context directly

979

function MyComponent() {

980

const queryClient = useQueryClient()

981

// Hook provides better error messages and type safety

982

}

983

984

// ❌ Don't use context directly unless necessary

985

function MyComponent() {

986

const queryClient = useContext(QueryClientContext)

987

// Manual error checking required

988

if (!queryClient) throw new Error('...')

989

}

990

```

991

992

The provider and context system in React Query offers a robust foundation for managing query state, error handling, and SSR hydration across your entire React application.