Skip to content

Commit 1abc446

Browse files
committed
Add documentation
1 parent fe15863 commit 1abc446

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-0
lines changed

docs/DataProviders.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,3 +884,136 @@ export default App;
884884
```
885885

886886
**Tip**: This example uses the function version of `setState` (`setDataProvider(() => dataProvider)`) instead of the more classic version (`setDataProvider(dataProvider)`). This is because some legacy Data Providers are actually functions, and `setState` would call them immediately on mount.
887+
888+
---
889+
layout: default
890+
title: "Offline Support"
891+
---
892+
893+
## Offline Support
894+
895+
React Query supports offline/local-first applications. To enable it in your React Admin application, install the required React Query packages:
896+
897+
```sh
898+
yarn add @tanstack/react-query-persist-client @tanstack/query-sync-storage-persister
899+
```
900+
901+
Then, register default functions for React Admin mutations on the `QueryClient` to enable resumable mutations (mutations triggered while offline). React Admin provides the `addOfflineSupportToQueryClient` function for this:
902+
903+
```ts
904+
// in src/queryClient.ts
905+
import { addOfflineSupportToQueryClient } from 'react-admin';
906+
import { QueryClient } from '@tanstack/react-query';
907+
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
908+
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
909+
import { dataProvider } from './dataProvider';
910+
911+
export const queryClient = new QueryClient();
912+
913+
addOfflineSupportToQueryClient({
914+
queryClient,
915+
dataProvider,
916+
resources: ['posts', 'comments'],
917+
});
918+
```
919+
920+
Then, wrap your `<Admin>` inside a [`<PersistQueryClientProvider>`](https://tanstack.com/query/latest/docs/framework/react/plugins/persistQueryClient#persistqueryclientprovider):
921+
922+
{% raw %}
923+
```tsx
924+
// in src/App.tsx
925+
import { Admin, Resource } from 'react-admin';
926+
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
927+
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
928+
import { queryClient } from './queryClient';
929+
import { dataProvider } from './dataProvider';
930+
import { posts } from './posts';
931+
import { comments } from './comments';
932+
933+
const localStoragePersister = createSyncStoragePersister({
934+
storage: window.localStorage,
935+
});
936+
937+
export const App = () => (
938+
<PersistQueryClientProvider
939+
client={queryClient}
940+
persistOptions={{ persister: localStoragePersister }}
941+
onSuccess={() => {
942+
// resume mutations after initial restore from localStorage is successful
943+
queryClient.resumePausedMutations();
944+
}}
945+
>
946+
<Admin queryClient={queryClient} dataProvider={dataProvider}>
947+
<Resource name="posts" {...posts} />
948+
<Resource name="comments" {...comments} />
949+
</Admin>
950+
</PersistQueryClientProvider>
951+
)
952+
```
953+
{% endraw %}
954+
955+
If you have [custom mutations](./Actions.md#calling-custom-methods) on your dataProvider, you can enable offline support for them too. For instance, if your `dataProvider` exposes a `banUser()` method:
956+
957+
```ts
958+
const dataProvider = {
959+
getList: /* ... */,
960+
getOne: /* ... */,
961+
getMany: /* ... */,
962+
getManyReference: /* ... */,
963+
create: /* ... */,
964+
update: /* ... */,
965+
updateMany: /* ... */,
966+
delete: /* ... */,
967+
deleteMany: /* ... */,
968+
banUser: (userId: string) => {
969+
return fetch(`/api/user/${userId}/ban`, { method: 'POST' })
970+
.then(response => response.json());
971+
},
972+
}
973+
974+
export type MyDataProvider = DataProvider & {
975+
banUser: (userId: string) => Promise<{ data: RaRecord }>
976+
}
977+
```
978+
979+
First, you must set a `mutationKey` for this mutation:
980+
981+
{% raw %}
982+
```tsx
983+
const BanUserButton = ({ userId }: { userId: string }) => {
984+
const dataProvider = useDataProvider();
985+
const { mutate, isPending } = useMutation({
986+
mutationKey: 'banUser'
987+
mutationFn: (userId) => dataProvider.banUser(userId)
988+
});
989+
return <Button label="Ban" onClick={() => mutate(userId)} disabled={isPending} />;
990+
};
991+
```
992+
{% endraw %}
993+
994+
**Tip**: Note that unlike the [_Calling Custom Methods_ example](./Actions.md#calling-custom-methods), we passed `userId` to the `mutate` function. This is necessary so that React Query passes it too to the default function when resuming the mutation.
995+
996+
Then, register a default function for it:
997+
998+
```ts
999+
// in src/queryClient.ts
1000+
import { addOfflineSupportToQueryClient } from 'react-admin';
1001+
import { QueryClient } from '@tanstack/react-query';
1002+
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
1003+
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
1004+
import { dataProvider } from './dataProvider';
1005+
1006+
export const queryClient = new QueryClient();
1007+
1008+
addOfflineSupportToQueryClient({
1009+
queryClient,
1010+
dataProvider,
1011+
resources: ['posts', 'comments'],
1012+
});
1013+
1014+
queryClient.setMutationDefaults('banUser', {
1015+
mutationFn: async (userId) => {
1016+
return dataProviderFn.banUser(userId);
1017+
},
1018+
});
1019+
```

0 commit comments

Comments
 (0)