Skip to content

Commit

Permalink
Merge pull request #9481 from marmelab/react-query-v5-doc
Browse files Browse the repository at this point in the history
Update documentation for react-query v5
  • Loading branch information
fzaninotto committed Dec 15, 2023
2 parents dd86c97 + 1f07756 commit 33a211f
Show file tree
Hide file tree
Showing 69 changed files with 782 additions and 535 deletions.
581 changes: 384 additions & 197 deletions docs/Actions.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions docs/Admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ const App = () => (

## `queryClient`

React-admin uses [react-query](https://react-query-v3.tanstack.com/) to fetch, cache and update data. Internally, the `<Admin>` component creates a react-query [`QueryClient`](https://tanstack.com/query/v3/docs/react/reference/QueryClient) on mount, using [react-query's "aggressive but sane" defaults](https://react-query-v3.tanstack.com/guides/important-defaults):
React-admin uses [React Query](https://tanstack.com/query/v5/) to fetch, cache and update data. Internally, the `<Admin>` component creates a react-query [`QueryClient`](https://tanstack.com/query/v5/docs/react/reference/QueryClient) on mount, using [react-query's "aggressive but sane" defaults](https://tanstack.com/query/v5/docs/react/guides/important-defaults):

* Queries consider cached data as stale
* Stale queries are refetched automatically in the background when:
Expand All @@ -707,7 +707,7 @@ If you want to override the react-query default query and mutation default optio

```tsx
import { Admin } from 'react-admin';
import { QueryClient } from 'react-query';
import { QueryClient } from '@tanstack/react-query';

const queryClient = new QueryClient({
defaultOptions: {
Expand All @@ -728,12 +728,12 @@ const App = () => (
);
```

To know which options you can pass to the `QueryClient` constructor, check the [react-query documentation](https://tanstack.com/query/v3/docs/react/reference/QueryClient) and the [query options](https://tanstack.com/query/v3/docs/react/reference/useQuery) and [mutation options](https://tanstack.com/query/v3/docs/react/reference/useMutation) sections.
To know which options you can pass to the `QueryClient` constructor, check the [react-query documentation](https://tanstack.com/query/v5/docs/react/reference/QueryClient) and the [query options](https://tanstack.com/query/v5/docs/react/reference/useQuery) and [mutation options](https://tanstack.com/query/v5/docs/react/reference/useMutation) sections.

The common settings that react-admin developers often overwrite are:

```tsx
import { QueryClient } from 'react-query';
import { QueryClient } from '@tanstack/react-query';

const queryClient = new QueryClient({
defaultOptions: {
Expand Down
10 changes: 5 additions & 5 deletions docs/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ const DeleteButton = () => {
const record = useRecordContext();
const {
open,
isLoading,
isPending,
handleDialogOpen,
handleDialogClose,
handleDelete,
Expand All @@ -240,7 +240,7 @@ const DeleteButton = () => {
</Button>
<Confirm
isOpen={open}
loading={isLoading}
loading={isPending}
title="ra.message.delete_title"
content="ra.message.delete_content"
translateOptions={{
Expand Down Expand Up @@ -339,8 +339,8 @@ export const ContactShow = () => (
);

const ContactShowContent = () => {
const { record, isLoading } = useShowContext<Contact>();
if (isLoading || !record) return null;
const { record, isPending } = useShowContext<Contact>();
if (isPending || !record) return null;
return (
<Box mt={2} display="flex">
<Box flex="1">
Expand Down Expand Up @@ -426,7 +426,7 @@ Many excellent open-source libraries already address partial requirements of B2B

Rather than reinventing the wheel, react-admin uses the best tools in each category (in terms of features, developer experience, active maintenance, documentation, user base), and provides a glue around these libraries.

In react-admin v4, these libraries are called [react-query](https://tanstack.com/query/v3), [react-router](https://reactrouter.com/en/main), [react-hook-form](https://react-hook-form.com/), [Material UI](https://mui.com/), [emotion](https://emotion.sh/docs/introduction), [testing-library](https://testing-library.com/docs/react-testing-library/intro), [date-fns](https://date-fns.org/), and [lodash](https://lodash.com/).
In react-admin v4, these libraries are called [React Query](https://tanstack.com/query/v3), [react-router](https://reactrouter.com/en/main), [react-hook-form](https://react-hook-form.com/), [Material UI](https://mui.com/), [emotion](https://emotion.sh/docs/introduction), [testing-library](https://testing-library.com/docs/react-testing-library/intro), [date-fns](https://date-fns.org/), and [lodash](https://lodash.com/).

When a new requirement arises, the react-admin teams always looks for an existing solution, and prefers integrating it rather than redeveloping it.

Expand Down
4 changes: 2 additions & 2 deletions docs/AuthProviderWriting.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,8 @@ React-admin uses the `fullName` and the `avatar` (an image source, or a data-uri
import { useGetIdentity, useGetOne } from 'react-admin';

const PostDetail = ({ id }) => {
const { data: post, isLoading: postLoading } = useGetOne('posts', { id });
const { identity, isLoading: identityLoading } = useGetIdentity();
const { data: post, isPending: postLoading } = useGetOne('posts', { id });
const { identity, isPending: identityLoading } = useGetIdentity();
if (postLoading || identityLoading) return <>Loading...</>;
if (!post.lockedBy || post.lockedBy === identity.id) {
// post isn't locked, or is locked by me
Expand Down
27 changes: 14 additions & 13 deletions docs/AutocompleteInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ The form value for the source must be the selected value, e.g.
| `emptyText` | Optional | `string` | `''` | The text to use for the empty element |
| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element |
| `filterToQuery` | Optional | `string` => `Object` | `q => ({ q })` | How to transform the searchText into a parameter for the data provider |
| `isLoading` | Optional | `boolean` | `false` | If `true`, the component will display a loading indicator. |
| `isPending` | Optional | `boolean` | `false` | If `true`, the component will display a loading indicator. |
| `inputText` | Optional | `Function` | `-` | Required if `optionText` is a custom Component, this function must return the text displayed for the current selection. |
| `matchSuggestion` | Optional | `Function` | `-` | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` |
| `onChange` | Optional | `Function` | `-` | A function called with the new value, along with the selected record, when the input value changes |
Expand Down Expand Up @@ -277,23 +277,23 @@ const filterToQuery = searchText => ({ name_ilike: `%${searchText}%` });
</ReferenceInput>
```

## `isLoading`
## `isPending`

When [fetching choices from a remote API](#fetching-choices), the `<AutocompleteInput>` can't be used until the choices are fetched. To let the user know, you can pass the `isLoading` prop to `<AutocompleteInput>`. This displays a loading message in the autocomplete box while the choices are being fetched.
When [fetching choices from a remote API](#fetching-choices), the `<AutocompleteInput>` can't be used until the choices are fetched. To let the user know, you can pass the `isPending` prop to `<AutocompleteInput>`. This displays a loading message in the autocomplete box while the choices are being fetched.

```jsx
import { useGetList, AutocompleteInput } from 'react-admin';

const UserCountry = () => {
const { data, isLoading } = useGetList('countries');
const { data, isPending } = useGetList('countries');
// data is an array of { id: 123, code: 'FR', name: 'France' }
return (
<AutocompleteInput
source="country"
choices={data}
optionText="name"
optionValue="code"
isLoading={isLoading}
isPending={isPending}
/>
);
}
Expand Down Expand Up @@ -601,21 +601,21 @@ You can use [`useGetList`](./useGetList.md) to fetch choices. For example, to fe
import { useGetList, AutocompleteInput } from 'react-admin';

const CountryInput = () => {
const { data, isLoading } = useGetList('countries');
const { data, isPending } = useGetList('countries');
// data is an array of { id: 123, code: 'FR', name: 'France' }
return (
<AutocompleteInput
source="country"
choices={data}
optionText="name"
optionValue="code"
isLoading={isLoading}
isPending={isPending}
/>
);
}
```

The `isLoading` prop is used to display a loading indicator while the data is being fetched.
The `isPending` prop is used to display a loading indicator while the data is being fetched.

However, most of the time, if you need to populate a `<AutocompleteInput>` with choices fetched from another resource, it's because you are trying to set a foreign key. In that case, you should use [`<ReferenceInput>`](./ReferenceInput.md) to fetch the choices instead (see next section).

Expand All @@ -633,25 +633,26 @@ import { useWatch } from 'react-hook-form';
const CompanyInput = () => {
const [filter, setFilter] = useState({ q: '' });
// fetch possible companies
const { data: choices, isLoading: isLoadingChoices } = useGetList('companies', { filter });
const { data: choices, isPending: isPendingChoices } = useGetList('companies', { filter });
// companies are like { id: 123, name: 'Acme' }
// get the current value of the foreign key
const companyId = useWatch({ name: 'company_id'})
// fetch the current company
const { data: currentCompany, isLoading: isLoadingCurrentCompany } = useGetOne('companies', { id: companyId });
const { data: currentCompany, isPending: isPendingCurrentCompany } = useGetOne('companies', { id: companyId });
// if the current company is not in the list of possible companies, add it
const choicesWithCurrentCompany = choices
? choices.find(choice => choice.id === companyId)
? choices
: [...choices, currentCompany]
: [];
const isPending = isPendingChoices && isPendingCurrentCompany;
return (
<AutocompleteInput
label="Company"
source="company_id"
choices={choicesWithCurrentCompany}
optionText="name"
disabled={isLoading}
disabled={isPending}
onInputChange={e => setFilter({ q: e.target.value })}
/>
);
Expand All @@ -677,10 +678,10 @@ const CompanyInput = () => (
`<ReferenceInput>` is a headless component that:

- fetches a list of records with `dataProvider.getList()` and `dataProvider.getOne()`, using the `reference` prop for the resource,
- puts the result of the fetch in the `ChoiceContext` as the `choices` prop, as well as the `isLoading` state,
- puts the result of the fetch in the `ChoiceContext` as the `choices` prop, as well as the `isPending` state,
- and renders its child component

When rendered as a child of `<ReferenceInput>`, `<AutocompleteInput>` reads that `ChoiceContext` to populate its own `choices` and `isLoading` props. It also sends the current input prop to the `useGetList` hook, so that the list of choices is filtered as the user types.
When rendered as a child of `<ReferenceInput>`, `<AutocompleteInput>` reads that `ChoiceContext` to populate its own `choices` and `isPending` props. It also sends the current input prop to the `useGetList` hook, so that the list of choices is filtered as the user types.

In fact, you can simplify the code even further:

Expand Down
2 changes: 1 addition & 1 deletion docs/Buttons.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ const PostList = () => (
| `children` | Required (*) | Element | - | A form component to render inside the Dialog |
| `DialogProps` | - | Object | - | Additional props to pass to the [MUI Dialog](https://mui.com/material-ui/react-dialog/) |
| `mutationMode` | - | `string` | `'pessimistic'` | The mutation mode (`'undoable'`, `'pessimistic'` or `'optimistic'`) |
| `mutationOptions` | - | Object | - | Mutation options passed to [react-query](https://tanstack.com/query/v3/docs/react/reference/useMutation) when calling `updateMany` |
| `mutationOptions` | - | Object | - | Mutation options passed to [React Query](https://tanstack.com/query/v5/docs/react/reference/useMutation) when calling `updateMany` |


#### `children`
Expand Down
4 changes: 2 additions & 2 deletions docs/Caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Finally, if your API uses GraphQL, it probably doesn't offer HTTP caching.
React-admin uses react-query for data fetching. React-query comes with its own caching system, allowing you to skip API calls completely. React-admin calls this the *application cache*. It's a good way to overcome the limitations if the HTTP cache. **This cache is opt-in** - you have to enable it by setting a custom `queryClient` in your `<Admin>` with a specific `staleTime` option.

```jsx
import { QueryClient } from 'react-query';
import { QueryClient } from '@tanstack/react-query';
import { Admin, Resource } from 'react-admin';

const App = () => {
Expand All @@ -110,7 +110,7 @@ const App = () => {

With this setting, all queries will be considered valid for 5 minutes. That means that react-admin *won't refetch* data from the API if the data is already in the cache and younger than 5 minutes.

Check the details about this cache [in the react-query documentation](https://react-query-v3.tanstack.com/guides/caching).
Check the details about this cache [in the react-query documentation](https://tanstack.com/query/v5/docs/react/guides/caching).

It especially fits admins for API backends with a small number of users (because with a large number of users, there is a high chance that a record kept in the client-side cache for a few minutes may be updated on the backend by another user). It also works with GraphQL APIs.

Expand Down
2 changes: 1 addition & 1 deletion docs/Community.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ If your problem is related to a library used by react-admin, you should ask for

* Material UI: [Documentation](https://mui.com/material-ui/getting-started/), [Support](https://mui.com/material-ui/getting-started/support/)
* react-router: [Documentation](https://reactrouter.com/en/main), [Discord](https://rmx.as/discord)
* react-query: [Documentation](https://tanstack.com/query/v3/docs/react/overview), [Discord](https://tlinz.com/discord)
* react-query: [Documentation](https://tanstack.com/query/v5/docs/react/overview), [Discord](https://tlinz.com/discord)
* react-hook-form: [Documentation](https://react-hook-form.com/get-started), [Discord](https://discord.gg/yYv7GZ8)
* emotion: [Documentation](https://emotion.sh/docs/introduction), [Slack](https://join.slack.com/t/emotion-slack/shared_invite/zt-rmtwsy74-2uvyFdz5uxa8OiMguJJeuQ), [Community](https://emotion.sh/docs/community)

Expand Down
4 changes: 2 additions & 2 deletions docs/Confirm.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const BulkResetViewsButton = () => {
const record = useRecordContext();
const [open, setOpen] = useState(false);

const [remove, { isLoading }] = useDelete(
const [remove, { isPending }] = useDelete(
'posts',
{ id: record && record.id }
);
Expand All @@ -45,7 +45,7 @@ const BulkResetViewsButton = () => {
<Button label="Delete" onClick={handleClick} />
<Confirm
isOpen={open}
loading={isLoading}
loading={isPending}
title={`Delete post #${record && record.id}`}
content="Are you sure you want to delete this item?"
onConfirm={handleConfirm}
Expand Down
2 changes: 1 addition & 1 deletion docs/Create.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ const PostCreate = () => (
```
{% endraw %}

You can also use `mutationOptions` to override success or error side effects, by setting the `mutationOptions` prop. Refer to the [useMutation documentation](https://tanstack.com/query/v3/docs/react/reference/useMutation) in the react-query website for a list of the possible options.
You can also use `mutationOptions` to override success or error side effects, by setting the `mutationOptions` prop. Refer to the [useMutation documentation](https://tanstack.com/query/v5/docs/react/reference/useMutation) in the react-query website for a list of the possible options.

Let's see an example with the success side effect. By default, when the save action succeeds, react-admin shows a notification, and redirects to the new record edit page. You can override this behavior and pass custom success side effects by providing a `mutationOptions` prop with an `onSuccess` key:

Expand Down
2 changes: 1 addition & 1 deletion docs/DataProviderWriting.md
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ All data provider methods accept a `meta` parameter. React-admin core components
For instance, you could pass an option to embed related records in the response:

```jsx
const { data, isLoading, error } = useGetOne(
const { data, isPending, error } = useGetOne(
'books',
{ id, meta: { _embed: 'authors' } },
);
Expand Down
Loading

0 comments on commit 33a211f

Please sign in to comment.