or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

admin-core.mdadvanced.mdauth.mddata-management.mddetail-views.mdforms-inputs.mdi18n.mdindex.mdlayout-navigation.mdlists-data-display.mdui-components.md

data-management.mddocs/

0

# Data Management

1

2

React Admin's data management system provides a powerful abstraction layer for backend communication through data providers and a rich set of hooks for querying and mutating data. Built on React Query, it offers optimistic updates, caching, background refetching, and error handling.

3

4

## Data Provider Interface

5

6

The data provider is the bridge between React Admin and your backend API. It defines a standard interface that React Admin uses for all data operations.

7

8

```typescript { .api }

9

import { DataProvider } from 'react-admin';

10

11

interface DataProvider {

12

getList: (resource: string, params: GetListParams) => Promise<GetListResult>;

13

getOne: (resource: string, params: GetOneParams) => Promise<GetOneResult>;

14

getMany: (resource: string, params: GetManyParams) => Promise<GetManyResult>;

15

getManyReference: (resource: string, params: GetManyReferenceParams) => Promise<GetManyReferenceResult>;

16

create: (resource: string, params: CreateParams) => Promise<CreateResult>;

17

update: (resource: string, params: UpdateParams) => Promise<UpdateResult>;

18

updateMany: (resource: string, params: UpdateManyParams) => Promise<UpdateManyResult>;

19

delete: (resource: string, params: DeleteParams) => Promise<DeleteResult>;

20

deleteMany: (resource: string, params: DeleteManyParams) => Promise<DeleteManyResult>;

21

}

22

```

23

24

### Query Parameter Types

25

26

```typescript { .api }

27

interface GetListParams {

28

pagination: { page: number; perPage: number };

29

sort: { field: string; order: 'ASC' | 'DESC' };

30

filter: any;

31

meta?: any;

32

}

33

34

interface GetOneParams {

35

id: Identifier;

36

meta?: any;

37

}

38

39

interface GetManyParams {

40

ids: Identifier[];

41

meta?: any;

42

}

43

44

interface GetManyReferenceParams {

45

target: string;

46

id: Identifier;

47

pagination: { page: number; perPage: number };

48

sort: { field: string; order: 'ASC' | 'DESC' };

49

filter: any;

50

meta?: any;

51

}

52

```

53

54

### Result Types

55

56

```typescript { .api }

57

interface GetListResult {

58

data: RaRecord[];

59

total?: number;

60

pageInfo?: {

61

hasNextPage?: boolean;

62

hasPreviousPage?: boolean;

63

};

64

}

65

66

interface GetOneResult {

67

data: RaRecord;

68

}

69

70

interface GetManyResult {

71

data: RaRecord[];

72

}

73

74

interface GetManyReferenceResult {

75

data: RaRecord[];

76

total?: number;

77

pageInfo?: {

78

hasNextPage?: boolean;

79

hasPreviousPage?: boolean;

80

};

81

}

82

```

83

84

### Mutation Parameter Types

85

86

```typescript { .api }

87

interface CreateParams {

88

data: Partial<RaRecord>;

89

meta?: any;

90

}

91

92

interface UpdateParams {

93

id: Identifier;

94

data: Partial<RaRecord>;

95

previousData: RaRecord;

96

meta?: any;

97

}

98

99

interface UpdateManyParams {

100

ids: Identifier[];

101

data: Partial<RaRecord>;

102

meta?: any;

103

}

104

105

interface DeleteParams {

106

id: Identifier;

107

previousData: RaRecord;

108

meta?: any;

109

}

110

111

interface DeleteManyParams {

112

ids: Identifier[];

113

meta?: any;

114

}

115

```

116

117

### Mutation Result Types

118

119

```typescript { .api }

120

interface CreateResult {

121

data: RaRecord;

122

}

123

124

interface UpdateResult {

125

data: RaRecord;

126

}

127

128

interface UpdateManyResult {

129

data?: Identifier[];

130

}

131

132

interface DeleteResult {

133

data: RaRecord;

134

}

135

136

interface DeleteManyResult {

137

data?: Identifier[];

138

}

139

```

140

141

## Data Provider Hooks

142

143

### useDataProvider

144

145

Access the data provider instance directly for custom operations.

146

147

```typescript { .api }

148

import { useDataProvider } from 'react-admin';

149

150

const useDataProvider: () => DataProvider;

151

```

152

153

#### Usage Example

154

155

```typescript

156

import { useDataProvider } from 'react-admin';

157

158

const MyComponent = () => {

159

const dataProvider = useDataProvider();

160

161

const handleCustomOperation = async () => {

162

try {

163

const result = await dataProvider.getList('posts', {

164

pagination: { page: 1, perPage: 10 },

165

sort: { field: 'title', order: 'ASC' },

166

filter: { status: 'published' }

167

});

168

console.log(result.data);

169

} catch (error) {

170

console.error('Error:', error);

171

}

172

};

173

174

return <button onClick={handleCustomOperation}>Load Posts</button>;

175

};

176

```

177

178

## Query Hooks

179

180

### useGetList

181

182

Fetch a list of records with pagination, sorting, and filtering.

183

184

```typescript { .api }

185

import { useGetList } from 'react-admin';

186

187

interface UseGetListOptions {

188

pagination?: { page: number; perPage: number };

189

sort?: { field: string; order: 'ASC' | 'DESC' };

190

filter?: any;

191

meta?: any;

192

enabled?: boolean;

193

staleTime?: number;

194

refetchInterval?: number;

195

}

196

197

const useGetList: <T extends RaRecord = any>(

198

resource: string,

199

options?: UseGetListOptions

200

) => {

201

data: T[] | undefined;

202

total: number | undefined;

203

pageInfo: PageInfo | undefined;

204

isLoading: boolean;

205

isFetching: boolean;

206

error: any;

207

refetch: () => void;

208

};

209

```

210

211

#### Usage Example

212

213

```typescript

214

import { useGetList } from 'react-admin';

215

216

const PostList = () => {

217

const {

218

data: posts,

219

total,

220

isLoading,

221

error,

222

refetch

223

} = useGetList('posts', {

224

pagination: { page: 1, perPage: 10 },

225

sort: { field: 'publishedAt', order: 'DESC' },

226

filter: { status: 'published' }

227

});

228

229

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

230

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

231

232

return (

233

<div>

234

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

235

<p>Total: {total} posts</p>

236

{posts?.map(post => (

237

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

238

))}

239

</div>

240

);

241

};

242

```

243

244

### useGetOne

245

246

Fetch a single record by ID.

247

248

```typescript { .api }

249

import { useGetOne } from 'react-admin';

250

251

interface GetOneParams {

252

id: Identifier;

253

meta?: any;

254

}

255

256

interface UseGetOneOptions {

257

enabled?: boolean;

258

staleTime?: number;

259

refetchInterval?: number;

260

}

261

262

const useGetOne: <T extends RaRecord = any>(

263

resource: string,

264

params: GetOneParams,

265

options?: UseGetOneOptions

266

) => {

267

data: T | undefined;

268

isLoading: boolean;

269

isFetching: boolean;

270

isSuccess: boolean;

271

isError: boolean;

272

error: any;

273

refetch: () => void;

274

status: 'idle' | 'loading' | 'error' | 'success';

275

};

276

```

277

278

#### Usage Example

279

280

```typescript

281

import { useGetOne } from 'react-admin';

282

283

const PostDetail = ({ id }: { id: string }) => {

284

const { data: post, isLoading, error } = useGetOne('posts', { id });

285

286

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

287

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

288

if (!post) return <div>Post not found</div>;

289

290

return (

291

<div>

292

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

293

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

294

</div>

295

);

296

};

297

```

298

299

### useGetMany

300

301

Fetch multiple records by their IDs.

302

303

```typescript { .api }

304

import { useGetMany } from 'react-admin';

305

306

interface UseGetManyOptions {

307

ids: Identifier[];

308

meta?: any;

309

enabled?: boolean;

310

}

311

312

const useGetMany: <T extends RaRecord = any>(

313

resource: string,

314

options: UseGetManyOptions

315

) => {

316

data: T[] | undefined;

317

isLoading: boolean;

318

isFetching: boolean;

319

error: any;

320

refetch: () => void;

321

};

322

```

323

324

### useGetManyAggregate

325

326

Fetch multiple records by their IDs with batching and deduplication. When multiple components call this hook with overlapping IDs in the same tick, it aggregates all requests into a single `getMany` call.

327

328

```typescript { .api }

329

import { useGetManyAggregate } from 'react-admin';

330

331

interface UseGetManyAggregateOptions {

332

ids: Identifier[];

333

meta?: any;

334

enabled?: boolean;

335

}

336

337

const useGetManyAggregate: <T extends RaRecord = any>(

338

resource: string,

339

options: UseGetManyAggregateOptions

340

) => {

341

data: T[] | undefined;

342

isLoading: boolean;

343

isFetching: boolean;

344

error: any;

345

refetch: () => void;

346

};

347

```

348

349

#### Usage Example

350

351

```typescript

352

import { useGetManyAggregate } from 'react-admin';

353

354

// If multiple components call this with overlapping IDs:

355

// Component A: useGetManyAggregate('tags', [1,2,3])

356

// Component B: useGetManyAggregate('tags', [3,4,5])

357

// Result: Single call to dataProvider.getMany('tags', [1,2,3,4,5])

358

359

const TagDisplay = ({ tagIds }: { tagIds: number[] }) => {

360

const { data: tags, isLoading } = useGetManyAggregate('tags', { ids: tagIds });

361

362

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

363

364

return (

365

<div>

366

{tags?.map(tag => (

367

<span key={tag.id} className="tag">{tag.name}</span>

368

))}

369

</div>

370

);

371

};

372

```

373

374

### useGetManyReference

375

376

Fetch records that reference another record.

377

378

```typescript { .api }

379

import { useGetManyReference } from 'react-admin';

380

381

interface UseGetManyReferenceOptions {

382

target: string;

383

id: Identifier;

384

pagination?: { page: number; perPage: number };

385

sort?: { field: string; order: 'ASC' | 'DESC' };

386

filter?: any;

387

meta?: any;

388

}

389

390

const useGetManyReference: <T extends RaRecord = any>(

391

resource: string,

392

options: UseGetManyReferenceOptions

393

) => {

394

data: T[] | undefined;

395

total: number | undefined;

396

pageInfo: PageInfo | undefined;

397

isLoading: boolean;

398

error: any;

399

refetch: () => void;

400

};

401

```

402

403

#### Usage Example

404

405

```typescript

406

import { useGetManyReference } from 'react-admin';

407

408

const UserComments = ({ userId }: { userId: string }) => {

409

const {

410

data: comments,

411

total,

412

isLoading

413

} = useGetManyReference('comments', {

414

target: 'userId',

415

id: userId,

416

sort: { field: 'createdAt', order: 'DESC' }

417

});

418

419

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

420

421

return (

422

<div>

423

<h3>{total} Comments</h3>

424

{comments?.map(comment => (

425

<div key={comment.id}>{comment.text}</div>

426

))}

427

</div>

428

);

429

};

430

```

431

432

### useInfiniteGetList

433

434

Fetch paginated data with infinite scrolling support.

435

436

```typescript { .api }

437

import { useInfiniteGetList } from 'react-admin';

438

439

const useInfiniteGetList: <T extends RaRecord = any>(

440

resource: string,

441

options?: UseGetListOptions

442

) => {

443

data: T[] | undefined;

444

total: number | undefined;

445

isLoading: boolean;

446

isFetching: boolean;

447

hasNextPage: boolean;

448

fetchNextPage: () => void;

449

error: any;

450

};

451

```

452

453

## Mutation Hooks

454

455

### useCreate

456

457

Create a new record.

458

459

```typescript { .api }

460

import { useCreate } from 'react-admin';

461

462

interface UseCreateOptions {

463

mutationMode?: 'pessimistic' | 'optimistic' | 'undoable';

464

returnPromise?: boolean;

465

onSuccess?: (data: any, variables: any, context: any) => void;

466

onError?: (error: any, variables: any, context: any) => void;

467

meta?: any;

468

}

469

470

const useCreate: <T extends RaRecord = any>(

471

resource?: string,

472

options?: UseCreateOptions

473

) => [

474

create: (resource?: string, params?: CreateParams, options?: UseCreateOptions) => Promise<T> | void,

475

{ data: T | undefined; isLoading: boolean; error: any }

476

];

477

```

478

479

#### Usage Example

480

481

```typescript

482

import { useCreate, useNotify, useRedirect } from 'react-admin';

483

484

const CreatePostButton = () => {

485

const notify = useNotify();

486

const redirect = useRedirect();

487

488

const [create, { isLoading }] = useCreate('posts', {

489

onSuccess: (data) => {

490

notify('Post created successfully');

491

redirect('show', 'posts', data.id);

492

},

493

onError: (error) => {

494

notify('Error creating post', { type: 'error' });

495

}

496

});

497

498

const handleCreate = () => {

499

create('posts', {

500

data: {

501

title: 'New Post',

502

content: 'Post content...',

503

status: 'draft'

504

}

505

});

506

};

507

508

return (

509

<button onClick={handleCreate} disabled={isLoading}>

510

{isLoading ? 'Creating...' : 'Create Post'}

511

</button>

512

);

513

};

514

```

515

516

### useUpdate

517

518

Update an existing record.

519

520

```typescript { .api }

521

import { useUpdate } from 'react-admin';

522

523

interface UseUpdateOptions {

524

mutationMode?: 'pessimistic' | 'optimistic' | 'undoable';

525

returnPromise?: boolean;

526

onSuccess?: (data: any, variables: any, context: any) => void;

527

onError?: (error: any, variables: any, context: any) => void;

528

meta?: any;

529

}

530

531

const useUpdate: <T extends RaRecord = any>(

532

resource?: string,

533

options?: UseUpdateOptions

534

) => [

535

update: (resource?: string, params?: UpdateParams, options?: UseUpdateOptions) => Promise<T> | void,

536

{ data: T | undefined; isLoading: boolean; error: any }

537

];

538

```

539

540

### useDelete

541

542

Delete a record.

543

544

```typescript { .api }

545

import { useDelete } from 'react-admin';

546

547

interface UseDeleteOptions {

548

mutationMode?: 'pessimistic' | 'optimistic' | 'undoable';

549

returnPromise?: boolean;

550

onSuccess?: (data: any, variables: any, context: any) => void;

551

onError?: (error: any, variables: any, context: any) => void;

552

meta?: any;

553

}

554

555

const useDelete: <T extends RaRecord = any>(

556

resource?: string,

557

options?: UseDeleteOptions

558

) => [

559

deleteOne: (resource?: string, params?: DeleteParams, options?: UseDeleteOptions) => Promise<T> | void,

560

{ data: T | undefined; isLoading: boolean; error: any }

561

];

562

```

563

564

### useUpdateMany & useDeleteMany

565

566

Bulk operations for updating or deleting multiple records.

567

568

```typescript { .api }

569

import { useUpdateMany, useDeleteMany } from 'react-admin';

570

571

const useUpdateMany: <T extends RaRecord = any>(

572

resource?: string,

573

options?: UseMutationOptions

574

) => [

575

updateMany: (resource?: string, params?: UpdateManyParams, options?: UseMutationOptions) => Promise<T> | void,

576

MutationResult

577

];

578

579

const useDeleteMany: <T extends RaRecord = any>(

580

resource?: string,

581

options?: UseMutationOptions

582

) => [

583

deleteMany: (resource?: string, params?: DeleteManyParams, options?: UseMutationOptions) => Promise<T> | void,

584

MutationResult

585

];

586

```

587

588

## Utility Hooks

589

590

### useRefresh

591

592

Refresh all queries and reload data.

593

594

```typescript { .api }

595

import { useRefresh } from 'react-admin';

596

597

const useRefresh: () => () => void;

598

```

599

600

#### Usage Example

601

602

```typescript

603

import { useRefresh } from 'react-admin';

604

605

const RefreshButton = () => {

606

const refresh = useRefresh();

607

608

return (

609

<button onClick={refresh}>

610

Refresh All Data

611

</button>

612

);

613

};

614

```

615

616

### useLoading

617

618

Access loading state across the application.

619

620

```typescript { .api }

621

import { useLoading } from 'react-admin';

622

623

const useLoading: () => boolean;

624

```

625

626

### useGetRecordId

627

628

Extract record ID from the current route.

629

630

```typescript { .api }

631

import { useGetRecordId } from 'react-admin';

632

633

const useGetRecordId: () => Identifier | undefined;

634

```

635

636

### useIsDataLoaded

637

638

Check if initial data has been loaded.

639

640

```typescript { .api }

641

import { useIsDataLoaded } from 'react-admin';

642

643

const useIsDataLoaded: () => boolean;

644

```

645

646

## Data Provider Utilities

647

648

### combineDataProviders

649

650

Combine multiple data providers for different resources or namespaces.

651

652

```typescript { .api }

653

import { combineDataProviders } from 'react-admin';

654

655

const combineDataProviders: (dataProviderMatcher: DataProviderMatcher) => DataProvider;

656

657

type DataProviderMatcher = (resource: string) => DataProvider;

658

```

659

660

#### Usage Example

661

662

```typescript

663

import { combineDataProviders } from 'react-admin';

664

import jsonServerProvider from 'ra-data-json-server';

665

import graphqlProvider from 'ra-data-graphql';

666

667

const postsProvider = jsonServerProvider('http://localhost:3001');

668

const usersProvider = graphqlProvider({ uri: 'http://localhost:4000/graphql' });

669

670

const dataProvider = combineDataProviders((resource: string) => {

671

switch (resource) {

672

case 'posts':

673

case 'comments':

674

return postsProvider;

675

case 'users':

676

case 'profiles':

677

return usersProvider;

678

default:

679

return postsProvider; // fallback

680

}

681

});

682

```

683

684

### withLifecycleCallbacks

685

686

Add lifecycle callbacks to data provider methods.

687

688

```typescript { .api }

689

import { withLifecycleCallbacks } from 'react-admin';

690

691

interface ResourceCallbacks<T extends RaRecord = any> {

692

resource: string;

693

// Read callbacks

694

beforeGetList?: (params: GetListParams, dataProvider: DataProvider) => Promise<GetListParams>;

695

afterGetList?: (result: GetListResult<T>, dataProvider: DataProvider) => Promise<GetListResult<T>>;

696

beforeGetOne?: (params: GetOneParams, dataProvider: DataProvider) => Promise<GetOneParams>;

697

afterGetOne?: (result: GetOneResult<T>, dataProvider: DataProvider) => Promise<GetOneResult<T>>;

698

beforeGetMany?: (params: GetManyParams, dataProvider: DataProvider) => Promise<GetManyParams>;

699

afterGetMany?: (result: GetManyResult<T>, dataProvider: DataProvider) => Promise<GetManyResult<T>>;

700

beforeGetManyReference?: (params: GetManyReferenceParams, dataProvider: DataProvider) => Promise<GetManyReferenceParams>;

701

afterGetManyReference?: (result: GetManyReferenceResult<T>, dataProvider: DataProvider) => Promise<GetManyReferenceResult<T>>;

702

703

// Mutation callbacks

704

beforeCreate?: (params: CreateParams, dataProvider: DataProvider) => Promise<CreateParams>;

705

afterCreate?: (result: CreateResult<T>, dataProvider: DataProvider) => Promise<CreateResult<T>>;

706

beforeUpdate?: (params: UpdateParams, dataProvider: DataProvider) => Promise<UpdateParams>;

707

afterUpdate?: (result: UpdateResult<T>, dataProvider: DataProvider) => Promise<UpdateResult<T>>;

708

beforeUpdateMany?: (params: UpdateManyParams, dataProvider: DataProvider) => Promise<UpdateManyParams>;

709

afterUpdateMany?: (result: UpdateManyResult, dataProvider: DataProvider) => Promise<UpdateManyResult>;

710

beforeDelete?: (params: DeleteParams, dataProvider: DataProvider) => Promise<DeleteParams>;

711

afterDelete?: (result: DeleteResult<T>, dataProvider: DataProvider) => Promise<DeleteResult<T>>;

712

beforeDeleteMany?: (params: DeleteManyParams, dataProvider: DataProvider) => Promise<DeleteManyParams>;

713

afterDeleteMany?: (result: DeleteManyResult, dataProvider: DataProvider) => Promise<DeleteManyResult>;

714

715

// Generic callbacks

716

beforeSave?: (data: Partial<T>, dataProvider: DataProvider) => Promise<Partial<T>>;

717

afterSave?: (record: T, dataProvider: DataProvider) => Promise<T>;

718

afterRead?: (record: T, dataProvider: DataProvider) => Promise<T>;

719

}

720

721

const withLifecycleCallbacks: (

722

dataProvider: DataProvider,

723

callbacks: ResourceCallbacks[]

724

) => DataProvider;

725

```

726

727

#### Usage Example

728

729

```typescript

730

import { withLifecycleCallbacks } from 'react-admin';

731

732

const dataProviderWithCallbacks = withLifecycleCallbacks(baseDataProvider, [

733

{

734

resource: 'posts',

735

beforeCreate: async (params, dataProvider) => {

736

// Add timestamp before creating

737

return {

738

...params,

739

data: { ...params.data, createdAt: new Date().toISOString() }

740

};

741

},

742

afterCreate: async (result, dataProvider) => {

743

// Log creation

744

console.log('Post created:', result.data);

745

return result;

746

},

747

beforeSave: async (data, dataProvider) => {

748

// Generic save callback (applies to create/update)

749

return { ...data, updatedAt: new Date().toISOString() };

750

}

751

},

752

{

753

resource: 'users',

754

afterRead: async (record, dataProvider) => {

755

// Transform user data after reading

756

return { ...record, fullName: `${record.firstName} ${record.lastName}` };

757

}

758

}

759

]);

760

```

761

762

### HttpError

763

764

Custom error class for HTTP-related errors.

765

766

```typescript { .api }

767

import { HttpError } from 'react-admin';

768

769

class HttpError extends Error {

770

status: number;

771

body?: any;

772

773

constructor(message: string, status: number, body?: any);

774

}

775

```

776

777

### Additional Data Provider Utilities

778

779

React Admin provides several additional utilities for working with data providers:

780

781

```typescript { .api }

782

import {

783

DataProviderContext,

784

testDataProvider,

785

fetchUtils,

786

undoableEventEmitter,

787

convertLegacyDataProvider

788

} from 'react-admin';

789

790

// Context for accessing data provider

791

const DataProviderContext: React.Context<DataProvider>;

792

793

// Test data provider for development/testing

794

const testDataProvider: DataProvider;

795

796

// Fetch utilities for HTTP requests

797

const fetchUtils: {

798

fetchJson: (url: string, options?: any) => Promise<{ status: number; headers: Headers; body: any; json: any }>;

799

queryParameters: (data: any) => string;

800

};

801

802

// Event emitter for undoable operations

803

const undoableEventEmitter: {

804

emit: (event: string, ...args: any[]) => void;

805

on: (event: string, listener: (...args: any[]) => void) => void;

806

off: (event: string, listener: (...args: any[]) => void) => void;

807

};

808

809

// Convert legacy data provider format to current

810

const convertLegacyDataProvider: (legacyDataProvider: any) => DataProvider;

811

```

812

813

#### Usage Example - fetchUtils

814

815

```typescript

816

import { fetchUtils } from 'react-admin';

817

818

const httpClient = (url: string, options: any = {}) => {

819

options.headers = new Headers({

820

Accept: 'application/json',

821

Authorization: `Bearer ${localStorage.getItem('token')}`,

822

...options.headers,

823

});

824

825

return fetchUtils.fetchJson(url, options);

826

};

827

828

// Use with data provider

829

const dataProvider = jsonServerProvider('http://localhost:3001', httpClient);

830

```

831

832

## Advanced Usage Examples

833

834

### Custom Data Provider Implementation

835

836

```typescript

837

import { DataProvider, HttpError } from 'react-admin';

838

839

const customDataProvider: DataProvider = {

840

getList: async (resource, params) => {

841

const { page, perPage } = params.pagination;

842

const { field, order } = params.sort;

843

844

const query = new URLSearchParams({

845

_page: page.toString(),

846

_limit: perPage.toString(),

847

_sort: field,

848

_order: order.toLowerCase(),

849

...params.filter,

850

});

851

852

const response = await fetch(`${apiUrl}/${resource}?${query}`);

853

854

if (!response.ok) {

855

throw new HttpError('Network error', response.status);

856

}

857

858

const data = await response.json();

859

const total = parseInt(response.headers.get('X-Total-Count') || '0');

860

861

return { data, total };

862

},

863

864

getOne: async (resource, params) => {

865

const response = await fetch(`${apiUrl}/${resource}/${params.id}`);

866

if (!response.ok) {

867

throw new HttpError('Record not found', response.status);

868

}

869

const data = await response.json();

870

return { data };

871

},

872

873

// ... implement other methods

874

};

875

```

876

877

### Optimistic Updates Example

878

879

```typescript

880

import { useUpdate, useNotify, useGetOne } from 'react-admin';

881

882

const OptimisticUpdateExample = ({ id }: { id: string }) => {

883

const notify = useNotify();

884

const { data: post } = useGetOne('posts', { id });

885

886

const [update, { isLoading }] = useUpdate('posts', {

887

mutationMode: 'optimistic', // Updates UI immediately

888

onSuccess: () => {

889

notify('Post updated successfully');

890

},

891

onError: (error) => {

892

notify('Update failed', { type: 'error' });

893

}

894

});

895

896

const handleTogglePublished = () => {

897

if (!post) return;

898

899

update('posts', {

900

id: post.id,

901

data: { published: !post.published },

902

previousData: post

903

});

904

};

905

906

return (

907

<button onClick={handleTogglePublished} disabled={isLoading}>

908

{post?.published ? 'Unpublish' : 'Publish'}

909

</button>

910

);

911

};

912

```

913

914

React Admin's data management system provides a robust foundation for handling all backend interactions while maintaining excellent user experience through optimistic updates, intelligent caching, and comprehensive error handling.