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.