or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

actions-reducers.mdasync-thunks.mdcore-store.mdentity-adapters.mdindex.mdmiddleware.mdreact-integration.mdrtk-query-react.mdrtk-query.mdutilities.md

rtk-query.mddocs/

0

# RTK Query Core

1

2

RTK Query provides a powerful data fetching solution with automatic caching, background updates, and comprehensive state management. Available from `@reduxjs/toolkit/query`.

3

4

## Capabilities

5

6

### Create API

7

8

Creates an RTK Query API slice with endpoints for data fetching and caching.

9

10

```typescript { .api }

11

/**

12

* Creates RTK Query API slice with endpoints for data fetching

13

* @param options - API configuration options

14

* @returns API object with endpoints, reducer, and middleware

15

*/

16

function createApi<

17

BaseQuery extends BaseQueryFn,

18

Definitions extends EndpointDefinitions,

19

ReducerPath extends string = 'api',

20

TagTypes extends string = never

21

>(options: CreateApiOptions<BaseQuery, Definitions, ReducerPath, TagTypes>): Api<BaseQuery, Definitions, ReducerPath, TagTypes>;

22

23

interface CreateApiOptions<BaseQuery, Definitions, ReducerPath, TagTypes> {

24

/** Unique key for the API slice in Redux state */

25

reducerPath?: ReducerPath;

26

27

/** Base query function for making requests */

28

baseQuery: BaseQuery;

29

30

/** Cache tag type names for invalidation */

31

tagTypes?: readonly TagTypes[];

32

33

/** Endpoint definitions */

34

endpoints: (builder: EndpointBuilder<BaseQuery, TagTypes, ReducerPath>) => Definitions;

35

36

/** Custom query argument serialization */

37

serializeQueryArgs?: SerializeQueryArgs<any>;

38

39

/** Global cache retention time in seconds */

40

keepUnusedDataFor?: number;

41

42

/** Global refetch behavior on mount or arg change */

43

refetchOnMountOrArgChange?: boolean | number;

44

45

/** Global refetch on window focus */

46

refetchOnFocus?: boolean;

47

48

/** Global refetch on network reconnect */

49

refetchOnReconnect?: boolean;

50

}

51

52

interface Api<BaseQuery, Definitions, ReducerPath, TagTypes> {

53

/** Generated endpoint objects with query/mutation methods */

54

endpoints: ApiEndpoints<Definitions, BaseQuery, TagTypes>;

55

56

/** Reducer function for the API slice */

57

reducer: Reducer<CombinedState<Definitions, TagTypes, ReducerPath>>;

58

59

/** Middleware for handling async operations */

60

middleware: Middleware<{}, any, Dispatch<AnyAction>>;

61

62

/** Utility functions */

63

util: {

64

/** Get running query promises */

65

getRunningOperationPromises(): Record<string, QueryActionCreatorResult<any> | MutationActionCreatorResult<any>>;

66

/** Reset API state */

67

resetApiState(): PayloadAction<void>;

68

/** Invalidate cache tags */

69

invalidateTags(tags: readonly TagDescription<TagTypes>[]): PayloadAction<TagDescription<TagTypes>[]>;

70

/** Select cache entries by tags */

71

selectCachedArgsForQuery<T>(state: any, endpointName: T): readonly unknown[];

72

/** Select invalidated cache entries */

73

selectInvalidatedBy(state: any, tags: readonly TagDescription<TagTypes>[]): readonly unknown[];

74

};

75

76

/** Internal cache key functions */

77

internalActions: {

78

onOnline(): PayloadAction<void>;

79

onOffline(): PayloadAction<void>;

80

onFocus(): PayloadAction<void>;

81

onFocusLost(): PayloadAction<void>;

82

};

83

}

84

```

85

86

**Usage Examples:**

87

88

```typescript

89

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';

90

91

// Basic API definition

92

const postsApi = createApi({

93

reducerPath: 'postsApi',

94

baseQuery: fetchBaseQuery({

95

baseUrl: '/api/',

96

}),

97

tagTypes: ['Post', 'User'],

98

endpoints: (builder) => ({

99

getPosts: builder.query<Post[], void>({

100

query: () => 'posts',

101

providesTags: ['Post']

102

}),

103

getPost: builder.query<Post, number>({

104

query: (id) => `posts/${id}`,

105

providesTags: (result, error, id) => [{ type: 'Post', id }]

106

}),

107

addPost: builder.mutation<Post, Partial<Post>>({

108

query: (newPost) => ({

109

url: 'posts',

110

method: 'POST',

111

body: newPost,

112

}),

113

invalidatesTags: ['Post']

114

}),

115

updatePost: builder.mutation<Post, { id: number; patch: Partial<Post> }>({

116

query: ({ id, patch }) => ({

117

url: `posts/${id}`,

118

method: 'PATCH',

119

body: patch,

120

}),

121

invalidatesTags: (result, error, { id }) => [{ type: 'Post', id }]

122

}),

123

deletePost: builder.mutation<{ success: boolean; id: number }, number>({

124

query: (id) => ({

125

url: `posts/${id}`,

126

method: 'DELETE',

127

}),

128

invalidatesTags: (result, error, id) => [{ type: 'Post', id }]

129

})

130

})

131

});

132

133

// Export hooks and actions

134

export const {

135

useGetPostsQuery,

136

useGetPostQuery,

137

useAddPostMutation,

138

useUpdatePostMutation,

139

useDeletePostMutation

140

} = postsApi;

141

142

// Configure store

143

const store = configureStore({

144

reducer: {

145

postsApi: postsApi.reducer,

146

},

147

middleware: (getDefaultMiddleware) =>

148

getDefaultMiddleware().concat(postsApi.middleware)

149

});

150

```

151

152

### Base Query Functions

153

154

RTK Query provides base query implementations for common scenarios.

155

156

```typescript { .api }

157

/**

158

* Built-in base query using fetch API

159

* @param options - Fetch configuration options

160

* @returns Base query function for HTTP requests

161

*/

162

function fetchBaseQuery(options?: FetchBaseQueryArgs): BaseQueryFn<string | FetchArgs, unknown, FetchBaseQueryError>;

163

164

interface FetchBaseQueryArgs {

165

/** Base URL for all requests */

166

baseUrl?: string;

167

168

/** Function to prepare request headers */

169

prepareHeaders?: (headers: Headers, api: { getState: () => unknown; extra: any; endpoint: string; type: 'query' | 'mutation'; forced?: boolean }) => Headers | void;

170

171

/** Custom fetch implementation */

172

fetchFn?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;

173

174

/** URL parameter serialization function */

175

paramsSerializer?: (params: Record<string, any>) => string;

176

177

/** Request timeout in milliseconds */

178

timeout?: number;

179

180

/** Response validation function */

181

validateStatus?: (response: Response, body: any) => boolean;

182

}

183

184

interface FetchArgs {

185

/** Request URL */

186

url: string;

187

/** HTTP method */

188

method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';

189

/** Request headers */

190

headers?: HeadersInit;

191

/** Request body */

192

body?: any;

193

/** URL parameters */

194

params?: Record<string, any>;

195

/** Response parsing method */

196

responseHandler?: 'content-type' | 'json' | 'text' | ((response: Response) => Promise<any>);

197

/** Response validation */

198

validateStatus?: (response: Response, body: any) => boolean;

199

/** Request timeout */

200

timeout?: number;

201

}

202

203

type FetchBaseQueryError =

204

| { status: number; data: any }

205

| { status: 'FETCH_ERROR'; error: string; data?: undefined }

206

| { status: 'PARSING_ERROR'; originalStatus: number; data: string; error: string }

207

| { status: 'TIMEOUT_ERROR'; error: string; data?: undefined }

208

| { status: 'CUSTOM_ERROR'; error: string; data?: any };

209

210

/**

211

* Placeholder base query for APIs that only use queryFn

212

* @returns Fake base query that throws error if used

213

*/

214

function fakeBaseQuery<ErrorType = string>(): BaseQueryFn<any, any, ErrorType>;

215

```

216

217

**Usage Examples:**

218

219

```typescript

220

// Basic fetch base query

221

const api = createApi({

222

baseQuery: fetchBaseQuery({

223

baseUrl: 'https://api.example.com/',

224

prepareHeaders: (headers, { getState }) => {

225

const token = (getState() as RootState).auth.token;

226

if (token) {

227

headers.set('authorization', `Bearer ${token}`);

228

}

229

headers.set('content-type', 'application/json');

230

return headers;

231

},

232

timeout: 10000

233

}),

234

endpoints: (builder) => ({

235

getData: builder.query<Data, string>({

236

query: (id) => ({

237

url: `data/${id}`,

238

params: { include: 'details' }

239

})

240

})

241

})

242

});

243

244

// Custom response handling

245

const apiWithCustomResponse = createApi({

246

baseQuery: fetchBaseQuery({

247

baseUrl: '/api/',

248

validateStatus: (response, result) => {

249

return response.ok && result?.success === true;

250

}

251

}),

252

endpoints: (builder) => ({

253

getProtectedData: builder.query<ProtectedData, void>({

254

query: () => ({

255

url: 'protected',

256

responseHandler: async (response) => {

257

if (!response.ok) {

258

throw new Error(`HTTP ${response.status}`);

259

}

260

const data = await response.json();

261

return data.payload; // Extract nested payload

262

}

263

})

264

})

265

})

266

});

267

268

// Using fakeBaseQuery with queryFn

269

const apiWithCustomLogic = createApi({

270

baseQuery: fakeBaseQuery<CustomError>(),

271

endpoints: (builder) => ({

272

getComplexData: builder.query<ComplexData, string>({

273

queryFn: async (arg, queryApi, extraOptions, baseQuery) => {

274

try {

275

// Custom logic for data fetching

276

const step1 = await customApiClient.fetchUserData(arg);

277

const step2 = await customApiClient.fetchUserPreferences(step1.id);

278

279

return { data: { ...step1, preferences: step2 } };

280

} catch (error) {

281

return { error: { status: 'CUSTOM_ERROR', error: error.message } };

282

}

283

}

284

})

285

})

286

});

287

```

288

289

### Endpoint Definitions

290

291

Define query and mutation endpoints with comprehensive configuration options.

292

293

```typescript { .api }

294

/**

295

* Builder object for defining endpoints

296

*/

297

interface EndpointBuilder<BaseQuery, TagTypes, ReducerPath> {

298

/** Define a query endpoint for data fetching */

299

query<ResultType, QueryArg = void>(

300

definition: QueryDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath>

301

): QueryDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath>;

302

303

/** Define a mutation endpoint for data modification */

304

mutation<ResultType, QueryArg = void>(

305

definition: MutationDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath>

306

): MutationDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath>;

307

308

/** Define an infinite query endpoint for paginated data */

309

infiniteQuery<ResultType, QueryArg = void, PageParam = unknown>(

310

definition: InfiniteQueryDefinition<QueryArg, BaseQuery, TagTypes, ResultType, PageParam, ReducerPath>

311

): InfiniteQueryDefinition<QueryArg, BaseQuery, TagTypes, ResultType, PageParam, ReducerPath>;

312

}

313

314

interface QueryDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath> {

315

/** Query function or args */

316

query: (arg: QueryArg) => any;

317

318

/** Custom query implementation */

319

queryFn?: (arg: QueryArg, api: QueryApi<ReducerPath, any, any, any>, extraOptions: any, baseQuery: BaseQuery) => Promise<QueryFnResult<ResultType>> | QueryFnResult<ResultType>;

320

321

/** Transform the response data */

322

transformResponse?: (baseQueryReturnValue: any, meta: any, arg: QueryArg) => ResultType | Promise<ResultType>;

323

324

/** Transform error responses */

325

transformErrorResponse?: (baseQueryReturnValue: any, meta: any, arg: QueryArg) => any;

326

327

/** Cache tags this query provides */

328

providesTags?: ResultDescription<TagTypes, ResultType, QueryArg>;

329

330

/** Cache retention time override */

331

keepUnusedDataFor?: number;

332

333

/** Lifecycle callback when query starts */

334

onQueryStarted?: (arg: QueryArg, api: QueryLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>) => Promise<void> | void;

335

336

/** Lifecycle callback when cache entry is added */

337

onCacheEntryAdded?: (arg: QueryArg, api: CacheLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>) => Promise<void> | void;

338

}

339

340

interface MutationDefinition<QueryArg, BaseQuery, TagTypes, ResultType, ReducerPath> {

341

/** Mutation function or args */

342

query: (arg: QueryArg) => any;

343

344

/** Custom mutation implementation */

345

queryFn?: (arg: QueryArg, api: QueryApi<ReducerPath, any, any, any>, extraOptions: any, baseQuery: BaseQuery) => Promise<QueryFnResult<ResultType>> | QueryFnResult<ResultType>;

346

347

/** Transform the response data */

348

transformResponse?: (baseQueryReturnValue: any, meta: any, arg: QueryArg) => ResultType | Promise<ResultType>;

349

350

/** Transform error responses */

351

transformErrorResponse?: (baseQueryReturnValue: any, meta: any, arg: QueryArg) => any;

352

353

/** Cache tags to invalidate */

354

invalidatesTags?: ResultDescription<TagTypes, ResultType, QueryArg>;

355

356

/** Lifecycle callback when mutation starts */

357

onQueryStarted?: (arg: QueryArg, api: MutationLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>) => Promise<void> | void;

358

359

/** Lifecycle callback when cache entry is added */

360

onCacheEntryAdded?: (arg: QueryArg, api: CacheLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>) => Promise<void> | void;

361

}

362

363

type ResultDescription<TagTypes, ResultType, QueryArg> =

364

| readonly TagTypes[]

365

| readonly TagDescription<TagTypes>[]

366

| ((result: ResultType | undefined, error: any, arg: QueryArg) => readonly TagDescription<TagTypes>[]);

367

368

interface TagDescription<TagTypes> {

369

type: TagTypes;

370

id?: string | number;

371

}

372

373

interface QueryFnResult<T> {

374

data?: T;

375

error?: any;

376

meta?: any;

377

}

378

```

379

380

**Usage Examples:**

381

382

```typescript

383

const api = createApi({

384

baseQuery: fetchBaseQuery({ baseUrl: '/api' }),

385

tagTypes: ['Post', 'User', 'Comment'],

386

endpoints: (builder) => ({

387

// Simple query

388

getPosts: builder.query<Post[], void>({

389

query: () => 'posts',

390

providesTags: ['Post']

391

}),

392

393

// Query with parameters

394

getPostsByUser: builder.query<Post[], { userId: string; limit?: number }>({

395

query: ({ userId, limit = 10 }) => ({

396

url: 'posts',

397

params: { userId, limit }

398

}),

399

providesTags: (result, error, { userId }) => [

400

'Post',

401

{ type: 'User', id: userId }

402

]

403

}),

404

405

// Query with response transformation

406

getPostWithComments: builder.query<PostWithComments, string>({

407

query: (id) => `posts/${id}`,

408

transformResponse: (response: { post: Post; comments: Comment[] }) => ({

409

...response.post,

410

comments: response.comments.sort((a, b) =>

411

new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()

412

)

413

}),

414

providesTags: (result, error, id) => [

415

{ type: 'Post', id },

416

{ type: 'Comment', id: 'LIST' }

417

]

418

}),

419

420

// Custom query function

421

getAnalytics: builder.query<Analytics, { startDate: string; endDate: string }>({

422

queryFn: async (arg, queryApi, extraOptions, baseQuery) => {

423

try {

424

// Multiple API calls

425

const [posts, users, comments] = await Promise.all([

426

baseQuery(`analytics/posts?start=${arg.startDate}&end=${arg.endDate}`),

427

baseQuery(`analytics/users?start=${arg.startDate}&end=${arg.endDate}`),

428

baseQuery(`analytics/comments?start=${arg.startDate}&end=${arg.endDate}`)

429

]);

430

431

if (posts.error || users.error || comments.error) {

432

return { error: 'Failed to fetch analytics data' };

433

}

434

435

return {

436

data: {

437

posts: posts.data,

438

users: users.data,

439

comments: comments.data,

440

summary: calculateSummary(posts.data, users.data, comments.data)

441

}

442

};

443

} catch (error) {

444

return { error: error.message };

445

}

446

}

447

}),

448

449

// Mutation with optimistic updates

450

updatePost: builder.mutation<Post, { id: string; patch: Partial<Post> }>({

451

query: ({ id, patch }) => ({

452

url: `posts/${id}`,

453

method: 'PATCH',

454

body: patch

455

}),

456

onQueryStarted: async ({ id, patch }, { dispatch, queryFulfilled }) => {

457

// Optimistic update

458

const patchResult = dispatch(

459

api.util.updateQueryData('getPost', id, (draft) => {

460

Object.assign(draft, patch);

461

})

462

);

463

464

try {

465

await queryFulfilled;

466

} catch {

467

// Revert on failure

468

patchResult.undo();

469

}

470

},

471

invalidatesTags: (result, error, { id }) => [{ type: 'Post', id }]

472

}),

473

474

// Mutation with side effects

475

deletePost: builder.mutation<void, string>({

476

query: (id) => ({

477

url: `posts/${id}`,

478

method: 'DELETE'

479

}),

480

onQueryStarted: async (id, { dispatch, queryFulfilled }) => {

481

try {

482

await queryFulfilled;

483

484

// Additional side effects

485

dispatch(showNotification('Post deleted successfully'));

486

dispatch(api.util.invalidateTags([{ type: 'Post', id }]));

487

} catch (error) {

488

dispatch(showNotification('Failed to delete post'));

489

}

490

}

491

})

492

})

493

});

494

```

495

496

### Cache Management

497

498

RTK Query provides sophisticated caching with tag-based invalidation and cache lifecycle management.

499

500

```typescript { .api }

501

/**

502

* Skip query execution when passed as argument

503

*/

504

const skipToken: unique symbol;

505

506

/**

507

* Query status enumeration

508

*/

509

enum QueryStatus {

510

uninitialized = 'uninitialized',

511

pending = 'pending',

512

fulfilled = 'fulfilled',

513

rejected = 'rejected'

514

}

515

516

/**

517

* Cache state structure for queries and mutations

518

*/

519

interface QueryState<T> {

520

/** Query status */

521

status: QueryStatus;

522

/** Query result data */

523

data?: T;

524

/** Current request ID */

525

requestId?: string;

526

/** Error information */

527

error?: any;

528

/** Fulfillment timestamp */

529

fulfilledTimeStamp?: number;

530

/** Start timestamp */

531

startedTimeStamp?: number;

532

/** End timestamp */

533

endedTimeStamp?: number;

534

}

535

536

interface MutationState<T> {

537

/** Mutation status */

538

status: QueryStatus;

539

/** Mutation result data */

540

data?: T;

541

/** Current request ID */

542

requestId?: string;

543

/** Error information */

544

error?: any;

545

}

546

```

547

548

**Usage Examples:**

549

550

```typescript

551

// Using skipToken to conditionally skip queries

552

const UserProfile = ({ userId }: { userId?: string }) => {

553

const { data: user, error, isLoading } = useGetUserQuery(

554

userId ?? skipToken // Skip query if no userId

555

);

556

557

if (!userId) {

558

return <div>Please select a user</div>;

559

}

560

561

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

562

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

563

564

return <div>{user?.name}</div>;

565

};

566

567

// Manual cache updates

568

const PostsList = () => {

569

const { data: posts } = useGetPostsQuery();

570

const [updatePost] = useUpdatePostMutation();

571

572

const handleOptimisticUpdate = (id: string, changes: Partial<Post>) => {

573

// Manually update cache

574

store.dispatch(

575

api.util.updateQueryData('getPosts', undefined, (draft) => {

576

const post = draft.find(p => p.id === id);

577

if (post) {

578

Object.assign(post, changes);

579

}

580

})

581

);

582

583

// Then perform actual update

584

updatePost({ id, patch: changes });

585

};

586

587

return (

588

<div>

589

{posts?.map(post => (

590

<PostItem

591

key={post.id}

592

post={post}

593

onUpdate={handleOptimisticUpdate}

594

/>

595

))}

596

</div>

597

);

598

};

599

600

// Prefetching data

601

const PostsListWithPrefetch = () => {

602

const dispatch = useAppDispatch();

603

604

useEffect(() => {

605

// Prefetch data

606

dispatch(api.util.prefetch('getPosts', undefined, { force: false }));

607

608

// Prefetch related data

609

dispatch(api.util.prefetch('getUsers', undefined));

610

}, [dispatch]);

611

612

const { data: posts } = useGetPostsQuery();

613

614

return <div>...</div>;

615

};

616

```

617

618

## Advanced Features

619

620

### Streaming Updates

621

622

```typescript

623

// Real-time updates with cache entry lifecycle

624

const api = createApi({

625

baseQuery: fetchBaseQuery({ baseUrl: '/api' }),

626

endpoints: (builder) => ({

627

getPosts: builder.query<Post[], void>({

628

query: () => 'posts',

629

onCacheEntryAdded: async (

630

arg,

631

{ updateCachedData, cacheDataLoaded, cacheEntryRemoved }

632

) => {

633

// Wait for initial data load

634

await cacheDataLoaded;

635

636

// Setup WebSocket connection

637

const ws = new WebSocket('ws://localhost:8080/posts');

638

639

ws.addEventListener('message', (event) => {

640

const update = JSON.parse(event.data);

641

642

updateCachedData((draft) => {

643

switch (update.type) {

644

case 'POST_ADDED':

645

draft.push(update.post);

646

break;

647

case 'POST_UPDATED':

648

const index = draft.findIndex(p => p.id === update.post.id);

649

if (index !== -1) {

650

draft[index] = update.post;

651

}

652

break;

653

case 'POST_DELETED':

654

return draft.filter(p => p.id !== update.postId);

655

}

656

});

657

});

658

659

// Cleanup on cache entry removal

660

await cacheEntryRemoved;

661

ws.close();

662

}

663

})

664

})

665

});

666

```

667

668

### Request Deduplication and Polling

669

670

```typescript

671

const api = createApi({

672

baseQuery: fetchBaseQuery({ baseUrl: '/api' }),

673

endpoints: (builder) => ({

674

// Polling query

675

getLiveData: builder.query<LiveData, void>({

676

query: () => 'live-data',

677

// Automatic polling every 5 seconds

678

pollingInterval: 5000

679

}),

680

681

// Custom request deduplication

682

getExpensiveData: builder.query<ExpensiveData, string>({

683

query: (id) => `expensive-data/${id}`,

684

serializeQueryArgs: ({ queryArgs, endpointDefinition, endpointName }) => {

685

// Custom serialization for deduplication

686

return `${endpointName}(${queryArgs})`;

687

},

688

// Cache for 10 minutes

689

keepUnusedDataFor: 600

690

})

691

})

692

});

693

694

// Component with polling control

695

const LiveDataComponent = () => {

696

const [pollingEnabled, setPollingEnabled] = useState(true);

697

698

const { data, error, isLoading } = useGetLiveDataQuery(undefined, {

699

pollingInterval: pollingEnabled ? 5000 : 0,

700

skipPollingIfUnfocused: true

701

});

702

703

return (

704

<div>

705

<button onClick={() => setPollingEnabled(!pollingEnabled)}>

706

{pollingEnabled ? 'Stop' : 'Start'} Polling

707

</button>

708

{data && <pre>{JSON.stringify(data, null, 2)}</pre>}

709

</div>

710

);

711

};

712

```

713

714

### Background Refetching

715

716

```typescript

717

// Setup automatic refetching listeners

718

import { setupListeners } from '@reduxjs/toolkit/query';

719

720

// Enable refetching on focus/reconnect

721

setupListeners(store.dispatch, {

722

onFocus: () => console.log('Window focused'),

723

onFocusLost: () => console.log('Window focus lost'),

724

onOnline: () => console.log('Network online'),

725

onOffline: () => console.log('Network offline')

726

});

727

728

// API with custom refetch behavior

729

const api = createApi({

730

baseQuery: fetchBaseQuery({ baseUrl: '/api' }),

731

refetchOnFocus: true,

732

refetchOnReconnect: true,

733

endpoints: (builder) => ({

734

getCriticalData: builder.query<CriticalData, void>({

735

query: () => 'critical-data',

736

// Always refetch on mount

737

refetchOnMountOrArgChange: true

738

}),

739

740

getCachedData: builder.query<CachedData, void>({

741

query: () => 'cached-data',

742

// Only refetch if older than 5 minutes

743

refetchOnMountOrArgChange: 300

744

})

745

})

746

});

747

```

748

749

### RTK Query Utilities

750

751

Additional utility functions for advanced RTK Query usage.

752

753

```typescript { .api }

754

/**

755

* Default function for serializing query arguments into cache keys

756

* @param args - Query arguments to serialize

757

* @param endpointDefinition - Endpoint definition for context

758

* @param endpointName - Name of the endpoint

759

* @returns Serialized string representation of arguments

760

*/

761

function defaultSerializeQueryArgs(

762

args: any,

763

endpointDefinition: EndpointDefinition<any, any, any, any>,

764

endpointName: string

765

): string;

766

767

/**

768

* Copy value while preserving structural sharing for performance

769

* Used internally for immutable updates with minimal re-renders

770

* @param oldObj - Previous value

771

* @param newObj - New value to merge

772

* @returns Value with structural sharing where possible

773

*/

774

function copyWithStructuralSharing<T>(oldObj: T, newObj: T): T;

775

776

/**

777

* Retry utility for enhanced error handling in base queries

778

* @param baseQuery - Base query function to retry

779

* @param options - Retry configuration options

780

* @returns Enhanced base query with retry logic

781

*/

782

function retry<Args extends any, Return extends any, Error extends any>(

783

baseQuery: BaseQueryFn<Args, Return, Error>,

784

options: RetryOptions

785

): BaseQueryFn<Args, Return, Error>;

786

787

interface RetryOptions {

788

/** Maximum number of retry attempts */

789

maxRetries: number;

790

/** Function to determine if error should trigger retry */

791

retryCondition?: (error: any, args: any, extraOptions: any) => boolean;

792

/** Delay between retries in milliseconds */

793

backoff?: (attempt: number, maxRetries: number) => number;

794

}

795

```

796

797

**Usage Examples:**

798

799

```typescript

800

import {

801

defaultSerializeQueryArgs,

802

copyWithStructuralSharing,

803

retry

804

} from '@reduxjs/toolkit/query';

805

806

// Custom query argument serialization

807

const customApi = createApi({

808

baseQuery: fetchBaseQuery({ baseUrl: '/api' }),

809

serializeQueryArgs: ({ queryArgs, endpointDefinition, endpointName }) => {

810

// Use default serialization with custom prefix

811

const defaultKey = defaultSerializeQueryArgs(queryArgs, endpointDefinition, endpointName);

812

return `custom:${defaultKey}`;

813

},

814

endpoints: (builder) => ({

815

getUser: builder.query<User, { userId: string; include?: string[] }>({

816

query: ({ userId, include = [] }) => ({

817

url: `users/${userId}`,

818

params: { include: include.join(',') }

819

})

820

})

821

})

822

});

823

824

// Enhanced base query with retry logic

825

const resilientBaseQuery = retry(

826

fetchBaseQuery({ baseUrl: '/api' }),

827

{

828

maxRetries: 3,

829

retryCondition: (error, args, extraOptions) => {

830

// Retry on network errors and 5xx server errors

831

return error.status >= 500 || error.name === 'NetworkError';

832

},

833

backoff: (attempt, maxRetries) => {

834

// Exponential backoff: 1s, 2s, 4s

835

return Math.min(1000 * (2 ** attempt), 10000);

836

}

837

}

838

);

839

840

const apiWithRetry = createApi({

841

baseQuery: resilientBaseQuery,

842

endpoints: (builder) => ({

843

getCriticalData: builder.query<Data, void>({

844

query: () => 'critical-data'

845

})

846

})

847

});

848

849

// Structural sharing in transformResponse

850

const optimizedApi = createApi({

851

baseQuery: fetchBaseQuery({ baseUrl: '/api' }),

852

endpoints: (builder) => ({

853

getOptimizedData: builder.query<ProcessedData, void>({

854

query: () => 'data',

855

transformResponse: (response: RawData, meta, arg) => {

856

const processed = processData(response);

857

// copyWithStructuralSharing is used internally by RTK Query

858

// but can be useful for custom transform logic

859

return {

860

...processed,

861

metadata: {

862

fetchedAt: Date.now(),

863

version: response.version

864

}

865

};

866

}

867

})

868

})

869

});

870

```