Skip to content

Commit a818941

Browse files
committed
Add Headless documentation
1 parent 1cc29b8 commit a818941

1 file changed

Lines changed: 130 additions & 0 deletions

File tree

docs_headless/src/content/docs/DataProviders.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,3 +876,133 @@ export default App;
876876
```
877877

878878
**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.
879+
880+
## Offline Support
881+
882+
React-admin supports offline/local-first applications. To enable this feature, install the following react-query packages:
883+
884+
```sh
885+
yarn add @tanstack/react-query-persist-client @tanstack/query-async-storage-persister
886+
```
887+
888+
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:
889+
890+
```ts
891+
// in src/queryClient.ts
892+
import { addOfflineSupportToQueryClient } from 'ra-core';
893+
import { QueryClient } from '@tanstack/react-query';
894+
import { dataProvider } from './dataProvider';
895+
896+
const baseQueryClient = new QueryClient();
897+
898+
export const queryClient = addOfflineSupportToQueryClient({
899+
queryClient: baseQueryClient,
900+
dataProvider,
901+
resources: ['posts', 'comments'],
902+
});
903+
```
904+
905+
Finally, wrap your `<Admin>` inside a [`<PersistQueryClientProvider>`](https://tanstack.com/query/latest/docs/framework/react/plugins/persistQueryClient#persistqueryclientprovider):
906+
907+
{% raw %}
908+
```tsx
909+
// in src/App.tsx
910+
import { CoreAdmin, Resource } from 'ra-core';
911+
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
912+
import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';
913+
import { queryClient } from './queryClient';
914+
import { dataProvider } from './dataProvider';
915+
import { posts } from './posts';
916+
import { comments } from './comments';
917+
918+
const localStoragePersister = createAsyncStoragePersister({
919+
storage: window.localStorage,
920+
});
921+
922+
export const App = () => (
923+
<PersistQueryClientProvider
924+
client={queryClient}
925+
persistOptions={{ persister: localStoragePersister }}
926+
onSuccess={() => {
927+
// resume mutations after initial restore from localStorage is successful
928+
queryClient.resumePausedMutations();
929+
}}
930+
>
931+
<CoreAdmin queryClient={queryClient} dataProvider={dataProvider}>
932+
<Resource name="posts" {...posts} />
933+
<Resource name="comments" {...comments} />
934+
</CoreAdmin>
935+
</PersistQueryClientProvider>
936+
)
937+
```
938+
{% endraw %}
939+
940+
This is enough to make all the standard react-admin features support offline scenarios.
941+
942+
## Adding Offline Support To Custom Mutations
943+
944+
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:
945+
946+
```ts
947+
const dataProvider = {
948+
getList: /* ... */,
949+
getOne: /* ... */,
950+
getMany: /* ... */,
951+
getManyReference: /* ... */,
952+
create: /* ... */,
953+
update: /* ... */,
954+
updateMany: /* ... */,
955+
delete: /* ... */,
956+
deleteMany: /* ... */,
957+
banUser: (userId: string) => {
958+
return fetch(`/api/user/${userId}/ban`, { method: 'POST' })
959+
.then(response => response.json());
960+
},
961+
}
962+
963+
export type MyDataProvider = DataProvider & {
964+
banUser: (userId: string) => Promise<{ data: RaRecord }>
965+
}
966+
```
967+
968+
First, you must set a `mutationKey` for this mutation:
969+
970+
{% raw %}
971+
```tsx
972+
const BanUserButton = ({ userId }: { userId: string }) => {
973+
const dataProvider = useDataProvider();
974+
const { mutate, isPending } = useMutation({
975+
mutationKey: ['banUser'],
976+
mutationFn: (userId) => dataProvider.banUser(userId)
977+
});
978+
return <button onClick={() => mutate(userId)} disabled={isPending}>Ban</button>;
979+
};
980+
```
981+
{% endraw %}
982+
983+
**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.
984+
985+
Then, register a default function for it:
986+
987+
```ts
988+
// in src/queryClient.ts
989+
import { addOfflineSupportToQueryClient } from 'ra-core';
990+
import { QueryClient } from '@tanstack/react-query';
991+
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
992+
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
993+
import { dataProvider } from './dataProvider';
994+
995+
const baseQueryClient = new QueryClient();
996+
997+
export const queryClient = addOfflineSupportToQueryClient({
998+
queryClient: baseQueryClient,
999+
dataProvider,
1000+
resources: ['posts', 'comments'],
1001+
});
1002+
1003+
queryClient.setMutationDefaults('banUser', {
1004+
mutationFn: async (userId) => {
1005+
return dataProvider.banUser(userId);
1006+
},
1007+
});
1008+
```

0 commit comments

Comments
 (0)