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

core-query-hooks.mddocs/

0

# Core Query Hooks

1

2

The foundation hooks for data fetching, caching, and synchronization in React Query.

3

4

## useQuery

5

6

**Primary hook for fetching, caching and updating asynchronous data**

7

8

```typescript { .api }

9

function useQuery<

10

TQueryFnData = unknown,

11

TError = DefaultError,

12

TData = TQueryFnData,

13

TQueryKey extends QueryKey = QueryKey,

14

>(

15

options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>,

16

queryClient?: QueryClient,

17

): UseQueryResult<TData, TError>

18

19

// With defined initial data

20

function useQuery<...>(

21

options: DefinedInitialDataOptions<...>,

22

queryClient?: QueryClient,

23

): DefinedUseQueryResult<TData, TError>

24

25

// With undefined initial data

26

function useQuery<...>(

27

options: UndefinedInitialDataOptions<...>,

28

queryClient?: QueryClient,

29

): UseQueryResult<TData, TError>

30

```

31

32

### Options

33

34

```typescript { .api }

35

interface UseQueryOptions<

36

TQueryFnData = unknown,

37

TError = DefaultError,

38

TData = TQueryFnData,

39

TQueryKey extends QueryKey = QueryKey,

40

> {

41

queryKey: TQueryKey

42

queryFn?: QueryFunction<TQueryFnData, TQueryKey> | SkipToken

43

enabled?: boolean

44

networkMode?: 'online' | 'always' | 'offlineFirst'

45

retry?: boolean | number | ((failureCount: number, error: TError) => boolean)

46

retryDelay?: number | ((retryAttempt: number, error: TError) => number)

47

staleTime?: number | ((query: Query) => number)

48

gcTime?: number

49

queryKeyHashFn?: QueryKeyHashFunction<TQueryKey>

50

refetchInterval?: number | false | ((query: Query) => number | false)

51

refetchIntervalInBackground?: boolean

52

refetchOnMount?: boolean | 'always' | ((query: Query) => boolean | 'always')

53

refetchOnWindowFocus?: boolean | 'always' | ((query: Query) => boolean | 'always')

54

refetchOnReconnect?: boolean | 'always' | ((query: Query) => boolean | 'always')

55

notifyOnChangeProps?: Array<keyof UseQueryResult> | 'all'

56

onSuccess?: (data: TData) => void

57

onError?: (error: TError) => void

58

onSettled?: (data: TData | undefined, error: TError | null) => void

59

select?: (data: TQueryFnData) => TData

60

suspense?: boolean

61

initialData?: TData | InitialDataFunction<TData>

62

initialDataUpdatedAt?: number | (() => number)

63

placeholderData?: TData | PlaceholderDataFunction<TData, TError>

64

structuralSharing?: boolean | ((oldData: TData | undefined, newData: TData) => TData)

65

throwOnError?: boolean | ((error: TError, query: Query) => boolean)

66

meta?: Record<string, unknown>

67

}

68

```

69

70

### Result

71

72

```typescript { .api }

73

interface UseQueryResult<TData = unknown, TError = DefaultError> {

74

data: TData | undefined

75

dataUpdatedAt: number

76

error: TError | null

77

errorUpdatedAt: number

78

failureCount: number

79

failureReason: TError | null

80

fetchStatus: 'fetching' | 'paused' | 'idle'

81

isError: boolean

82

isFetched: boolean

83

isFetchedAfterMount: boolean

84

isFetching: boolean

85

isInitialLoading: boolean

86

isLoading: boolean

87

isLoadingError: boolean

88

isPaused: boolean

89

isPending: boolean

90

isPlaceholderData: boolean

91

isRefetchError: boolean

92

isRefetching: boolean

93

isStale: boolean

94

isSuccess: boolean

95

refetch: (options?: RefetchOptions) => Promise<UseQueryResult<TData, TError>>

96

status: 'pending' | 'error' | 'success'

97

}

98

99

interface DefinedUseQueryResult<TData = unknown, TError = DefaultError>

100

extends Omit<UseQueryResult<TData, TError>, 'data'> {

101

data: TData

102

}

103

```

104

105

### Basic Usage

106

107

```typescript { .api }

108

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

109

110

interface User {

111

id: number

112

name: string

113

email: string

114

}

115

116

function UserProfile({ userId }: { userId: number }) {

117

const {

118

data: user,

119

isLoading,

120

isError,

121

error,

122

refetch

123

} = useQuery<User>({

124

queryKey: ['user', userId],

125

queryFn: async () => {

126

const response = await fetch(`/api/users/${userId}`)

127

if (!response.ok) {

128

throw new Error('Failed to fetch user')

129

}

130

return response.json()

131

},

132

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

133

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

134

})

135

136

if (isLoading) return <div>Loading user...</div>

137

if (isError) return <div>Error: {error.message}</div>

138

139

return (

140

<div>

141

<h1>{user?.name}</h1>

142

<p>{user?.email}</p>

143

<button onClick={() => refetch()}>Refresh</button>

144

</div>

145

)

146

}

147

```

148

149

### Advanced Usage

150

151

```typescript { .api }

152

// Conditional querying

153

function UserPosts({ userId }: { userId?: number }) {

154

const { data, isLoading } = useQuery({

155

queryKey: ['user-posts', userId],

156

queryFn: () => fetchUserPosts(userId!),

157

enabled: !!userId, // Only fetch when userId exists

158

retry: (failureCount, error) => {

159

// Don't retry on 404

160

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

161

return failureCount < 3

162

}

163

})

164

165

// Component logic...

166

}

167

168

// Data transformation

169

function PostsList() {

170

const { data: posts } = useQuery({

171

queryKey: ['posts'],

172

queryFn: fetchPosts,

173

select: (data) => data.posts.filter(post => post.published),

174

placeholderData: { posts: [] }

175

})

176

177

return (

178

<div>

179

{posts.map(post => (

180

<div key={post.id}>{post.title}</div>

181

))}

182

</div>

183

)

184

}

185

```

186

187

## useInfiniteQuery

188

189

**Hook for queries that can incrementally load more data (pagination, infinite scrolling)**

190

191

```typescript { .api }

192

function useInfiniteQuery<

193

TQueryFnData,

194

TError = DefaultError,

195

TData = InfiniteData<TQueryFnData>,

196

TQueryKey extends QueryKey = QueryKey,

197

TPageParam = unknown,

198

>(

199

options: UseInfiniteQueryOptions<TQueryFnData, TError, TData, TQueryKey, TPageParam>,

200

queryClient?: QueryClient,

201

): UseInfiniteQueryResult<TData, TError>

202

203

// With defined initial data

204

function useInfiniteQuery<...>(

205

options: DefinedInitialDataInfiniteOptions<...>,

206

queryClient?: QueryClient,

207

): DefinedUseInfiniteQueryResult<TData, TError>

208

209

// With undefined initial data

210

function useInfiniteQuery<...>(

211

options: UndefinedInitialDataInfiniteOptions<...>,

212

queryClient?: QueryClient,

213

): UseInfiniteQueryResult<TData, TError>

214

```

215

216

### Options

217

218

```typescript { .api }

219

interface UseInfiniteQueryOptions<

220

TQueryFnData = unknown,

221

TError = DefaultError,

222

TData = TQueryFnData,

223

TQueryKey extends QueryKey = QueryKey,

224

TPageParam = unknown,

225

> extends Omit<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, 'queryFn'> {

226

queryFn: QueryFunction<TQueryFnData, TQueryKey, TPageParam>

227

initialPageParam: TPageParam

228

getNextPageParam: (

229

lastPage: TQueryFnData,

230

allPages: TQueryFnData[],

231

lastPageParam: TPageParam,

232

allPageParams: TPageParam[]

233

) => TPageParam | undefined | null

234

getPreviousPageParam?: (

235

firstPage: TQueryFnData,

236

allPages: TQueryFnData[],

237

firstPageParam: TPageParam,

238

allPageParams: TPageParam[]

239

) => TPageParam | undefined | null

240

maxPages?: number

241

}

242

```

243

244

### Result

245

246

```typescript { .api }

247

interface UseInfiniteQueryResult<TData = unknown, TError = DefaultError>

248

extends Omit<UseQueryResult<TData, TError>, 'data'> {

249

data: InfiniteData<TData> | undefined

250

fetchNextPage: (options?: FetchNextPageOptions) => Promise<UseInfiniteQueryResult<TData, TError>>

251

fetchPreviousPage: (options?: FetchPreviousPageOptions) => Promise<UseInfiniteQueryResult<TData, TError>>

252

hasNextPage: boolean

253

hasPreviousPage: boolean

254

isFetchingNextPage: boolean

255

isFetchingPreviousPage: boolean

256

}

257

258

interface InfiniteData<TData, TPageParam = unknown> {

259

pages: TData[]

260

pageParams: TPageParam[]

261

}

262

```

263

264

### Basic Usage

265

266

```typescript { .api }

267

interface PostsPage {

268

posts: Post[]

269

nextCursor?: number

270

hasMore: boolean

271

}

272

273

function InfinitePostsList() {

274

const {

275

data,

276

fetchNextPage,

277

hasNextPage,

278

isFetchingNextPage,

279

isLoading,

280

error

281

} = useInfiniteQuery<PostsPage>({

282

queryKey: ['posts'],

283

queryFn: async ({ pageParam = 0 }) => {

284

const response = await fetch(`/api/posts?cursor=${pageParam}`)

285

return response.json()

286

},

287

initialPageParam: 0,

288

getNextPageParam: (lastPage) => {

289

return lastPage.hasMore ? lastPage.nextCursor : undefined

290

},

291

staleTime: 5 * 60 * 1000,

292

})

293

294

if (isLoading) return <div>Loading posts...</div>

295

if (error) return <div>Error: {error.message}</div>

296

297

return (

298

<div>

299

{data?.pages.map((page, i) => (

300

<div key={i}>

301

{page.posts.map((post) => (

302

<div key={post.id}>

303

<h3>{post.title}</h3>

304

<p>{post.excerpt}</p>

305

</div>

306

))}

307

</div>

308

))}

309

310

<button

311

onClick={() => fetchNextPage()}

312

disabled={!hasNextPage || isFetchingNextPage}

313

>

314

{isFetchingNextPage

315

? 'Loading more...'

316

: hasNextPage

317

? 'Load More'

318

: 'Nothing more to load'}

319

</button>

320

</div>

321

)

322

}

323

```

324

325

### Bidirectional Infinite Queries

326

327

```typescript { .api }

328

function BidirectionalFeed() {

329

const {

330

data,

331

fetchNextPage,

332

fetchPreviousPage,

333

hasNextPage,

334

hasPreviousPage,

335

isFetchingNextPage,

336

isFetchingPreviousPage

337

} = useInfiniteQuery({

338

queryKey: ['feed'],

339

queryFn: async ({ pageParam = { direction: 'next', cursor: 0 } }) => {

340

const { direction, cursor } = pageParam

341

const response = await fetch(`/api/feed?${direction}=${cursor}`)

342

return response.json()

343

},

344

initialPageParam: { direction: 'next', cursor: 0 },

345

getNextPageParam: (lastPage) =>

346

lastPage.hasMore

347

? { direction: 'next', cursor: lastPage.nextCursor }

348

: undefined,

349

getPreviousPageParam: (firstPage) =>

350

firstPage.hasPrevious

351

? { direction: 'previous', cursor: firstPage.previousCursor }

352

: undefined

353

})

354

355

return (

356

<div>

357

<button

358

onClick={() => fetchPreviousPage()}

359

disabled={!hasPreviousPage || isFetchingPreviousPage}

360

>

361

{isFetchingPreviousPage ? 'Loading...' : 'Load Previous'}

362

</button>

363

364

{data?.pages.map((page, i) => (

365

<div key={i}>

366

{page.items.map(item => (

367

<div key={item.id}>{item.content}</div>

368

))}

369

</div>

370

))}

371

372

<button

373

onClick={() => fetchNextPage()}

374

disabled={!hasNextPage || isFetchingNextPage}

375

>

376

{isFetchingNextPage ? 'Loading...' : 'Load Next'}

377

</button>

378

</div>

379

)

380

}

381

```

382

383

## useQueries

384

385

**Hook for running multiple queries in parallel with advanced composition capabilities**

386

387

```typescript { .api }

388

function useQueries<

389

T extends Array<any>,

390

TCombinedResult = QueriesResults<T>,

391

>(

392

options: {

393

queries: readonly [...QueriesOptions<T>]

394

combine?: (result: QueriesResults<T>) => TCombinedResult

395

subscribed?: boolean

396

},

397

queryClient?: QueryClient,

398

): TCombinedResult

399

```

400

401

### Types

402

403

```typescript { .api }

404

type QueriesOptions<T extends Array<any>> = {

405

[K in keyof T]: UseQueryOptions<any, any, any, any>

406

}

407

408

type QueriesResults<T extends Array<any>> = {

409

[K in keyof T]: UseQueryResult<any, any>

410

}

411

```

412

413

### Basic Usage

414

415

```typescript { .api }

416

function Dashboard({ userId }: { userId: number }) {

417

const results = useQueries({

418

queries: [

419

{

420

queryKey: ['user', userId],

421

queryFn: () => fetchUser(userId),

422

staleTime: 5 * 60 * 1000,

423

},

424

{

425

queryKey: ['user-posts', userId],

426

queryFn: () => fetchUserPosts(userId),

427

staleTime: 2 * 60 * 1000,

428

},

429

{

430

queryKey: ['user-followers', userId],

431

queryFn: () => fetchUserFollowers(userId),

432

enabled: userId > 0,

433

}

434

]

435

})

436

437

const [userQuery, postsQuery, followersQuery] = results

438

439

if (userQuery.isLoading) return <div>Loading user...</div>

440

if (userQuery.error) return <div>Error loading user</div>

441

442

return (

443

<div>

444

<h1>{userQuery.data?.name}</h1>

445

446

<section>

447

<h2>Posts</h2>

448

{postsQuery.isLoading ? (

449

<div>Loading posts...</div>

450

) : (

451

<div>{postsQuery.data?.length} posts</div>

452

)}

453

</section>

454

455

<section>

456

<h2>Followers</h2>

457

{followersQuery.isLoading ? (

458

<div>Loading followers...</div>

459

) : (

460

<div>{followersQuery.data?.length} followers</div>

461

)}

462

</section>

463

</div>

464

)

465

}

466

```

467

468

### With Combine Function

469

470

```typescript { .api }

471

function CombinedDashboard({ userIds }: { userIds: number[] }) {

472

const combinedResult = useQueries({

473

queries: userIds.map(id => ({

474

queryKey: ['user', id],

475

queryFn: () => fetchUser(id),

476

})),

477

combine: (results) => ({

478

users: results.map(result => result.data).filter(Boolean),

479

isLoading: results.some(result => result.isLoading),

480

hasErrors: results.some(result => result.isError),

481

errors: results.map(result => result.error).filter(Boolean),

482

})

483

})

484

485

if (combinedResult.isLoading) {

486

return <div>Loading users...</div>

487

}

488

489

if (combinedResult.hasErrors) {

490

return (

491

<div>

492

Errors occurred:

493

{combinedResult.errors.map((error, i) => (

494

<div key={i}>{error.message}</div>

495

))}

496

</div>

497

)

498

}

499

500

return (

501

<div>

502

<h1>Users ({combinedResult.users.length})</h1>

503

{combinedResult.users.map(user => (

504

<div key={user.id}>{user.name}</div>

505

))}

506

</div>

507

)

508

}

509

```

510

511

### Dynamic Queries

512

513

```typescript { .api }

514

function DynamicQueries({ searchTerms }: { searchTerms: string[] }) {

515

const queries = useQueries({

516

queries: searchTerms.map((term) => ({

517

queryKey: ['search', term],

518

queryFn: () => searchPosts(term),

519

enabled: term.length > 2, // Only search terms longer than 2 chars

520

staleTime: 30 * 1000, // 30 seconds

521

})),

522

combine: (results) => ({

523

data: results.flatMap(result => result.data || []),

524

isAnyLoading: results.some(result => result.isLoading),

525

hasData: results.some(result => result.data?.length > 0),

526

})

527

})

528

529

return (

530

<div>

531

{queries.isAnyLoading && <div>Searching...</div>}

532

{!queries.hasData && !queries.isAnyLoading && (

533

<div>No results found</div>

534

)}

535

{queries.data.map(post => (

536

<div key={post.id}>{post.title}</div>

537

))}

538

</div>

539

)

540

}

541

```

542

543

## Performance Optimizations

544

545

### Query Key Factories

546

547

```typescript { .api }

548

// Consistent query key management

549

const userKeys = {

550

all: ['users'] as const,

551

lists: () => [...userKeys.all, 'list'] as const,

552

list: (filters: string) => [...userKeys.lists(), { filters }] as const,

553

details: () => [...userKeys.all, 'detail'] as const,

554

detail: (id: number) => [...userKeys.details(), id] as const,

555

posts: (id: number) => [...userKeys.detail(id), 'posts'] as const,

556

}

557

558

// Usage with type safety

559

const { data: user } = useQuery({

560

queryKey: userKeys.detail(userId),

561

queryFn: () => fetchUser(userId)

562

})

563

564

const { data: posts } = useQuery({

565

queryKey: userKeys.posts(userId),

566

queryFn: () => fetchUserPosts(userId),

567

enabled: !!user

568

})

569

```

570

571

### Structural Sharing

572

573

```typescript { .api }

574

const { data } = useQuery({

575

queryKey: ['todos'],

576

queryFn: fetchTodos,

577

structuralSharing: (oldData, newData) => {

578

// Custom structural sharing logic

579

if (!oldData) return newData

580

581

// Only update if data actually changed

582

const changed = newData.some((todo, index) =>

583

!oldData[index] || todo.id !== oldData[index].id

584

)

585

586

return changed ? newData : oldData

587

}

588

})

589

```

590

591

### Selective Subscriptions

592

593

```typescript { .api }

594

const { data, refetch } = useQuery({

595

queryKey: ['user', userId],

596

queryFn: () => fetchUser(userId),

597

notifyOnChangeProps: ['data', 'error'], // Only re-render when data or error changes

598

})

599

```

600

601

These core query hooks provide the foundation for all data fetching patterns in React Query, offering powerful caching, background updates, error handling, and performance optimizations out of the box.