Skip to content

Commit

Permalink
Merge pull request #8821 from marmelab/doc-referenceinput
Browse files Browse the repository at this point in the history
[Doc] Improve usage examples for `<ReferenceInput>` and `<ReferenceArrayInput>`
  • Loading branch information
slax57 authored Apr 12, 2023
2 parents 9c997c1 + 79d0c35 commit 318cc33
Show file tree
Hide file tree
Showing 12 changed files with 564 additions and 86 deletions.
4 changes: 3 additions & 1 deletion docs/AutocompleteArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ In that case, set the `translateChoice` prop to `false`.

**Tip**: To use the `disableCloseOnSelect` prop, you must also set `blurOnSelect={false}`, since this is enabled by default.

## Using In A ReferenceArrayInput
## Fetching Choices

If you want to populate the `choices` attribute with a list of related records, you should decorate `<AutocompleteArrayInput>` with [`<ReferenceArrayInput>`](./ReferenceArrayInput.md), and leave the `choices` empty:

Expand All @@ -448,6 +448,8 @@ import { AutocompleteArrayInput, ReferenceArrayInput } from 'react-admin';
</ReferenceArrayInput>
```

Check [the `<ReferenceArrayInput>` documentation](./ReferenceArrayInput.md) for more details.

## Working With Object Values

When working with a field that contains an array of *objects*, use `parse` and `format` to turn the value into an array of scalar values.
Expand Down
206 changes: 175 additions & 31 deletions docs/AutocompleteInput.md

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions docs/CheckboxGroupInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,17 @@ However, in some cases (e.g. inside a `<ReferenceArrayInput>`), you may not want
```jsx
<CheckboxGroupInput source="roles" choices={choices} translateChoice={false}/>
```

## Fetching Choices

If you want to populate the `choices` attribute with a list of related records, you should decorate `<CheckboxGroupInput>` with [`<ReferenceArrayInput>`](./ReferenceArrayInput.md), and leave the `choices` empty:

```jsx
import { AutocompleteArrayInput, ReferenceArrayInput } from 'react-admin';

<ReferenceArrayInput label="Tags" reference="tags" source="tags">
<CheckboxGroupInput />
</ReferenceArrayInput>
```

Check [the `<ReferenceArrayInput>` documentation](./ReferenceArrayInput.md) for more details.
139 changes: 124 additions & 15 deletions docs/RadioButtonGroupInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ The form value for the source must be the selected value, e.g.

| Prop | Required | Type | Default | Description |
| ----------------- | -------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `choices` | Optional | `Object[]` | - | List of items to show as options. Required unless inside a ReferenceInput. |
| `choices` | Optional | `Object[]` | - | List of items to show as options. Required unless inside a ReferenceInput. |
| `isLoading` | Optional | `boolean` | `false` | If `true`, the component will display a loading indicator. |
| `options` | Optional | `Object` | - | Props to pass to the underlying `<RadioButtonGroup>` element |
| `optionText` | Optional | `string` &#124; `Function` | `name` | Field name of record to display in the suggestion item or function which accepts the current record as argument (`record => {string}`) |
| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
Expand Down Expand Up @@ -108,7 +109,7 @@ If you need to *fetch* the options from another resource, you're actually editin
</ReferenceInput>
```

See [Using in a `<ReferenceInput>`](#using-in-a-referenceinput) below for more information.
See [Selecting a foreign key](#selecting-a-foreign-key) below for more information.

If you have an *array of values* for the options, turn it into an array of objects with the `id` and `name` properties:

Expand All @@ -120,6 +121,28 @@ const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) }
<RadioButtonGroupInput source="category" choices={choices} />
```

## `isLoading`

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

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

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

## `options`

Use the `options` attribute if you want to override any of MUI's `<RadioGroup>` attributes:
Expand Down Expand Up @@ -158,7 +181,7 @@ import { RadioButtonGroupInput, ReferenceInput } from 'react-admin';
</ReferenceInput>
```

See [Using in a `<ReferenceInput>`](#using-in-a-referenceinput) below for more details.
See [fetching choices](#fetching-choices) below for more details.

`optionText` also accepts a function, so you can shape the option text at will:

Expand Down Expand Up @@ -247,24 +270,110 @@ However, in some cases, you may not want the choice to be translated. In that ca

Note that `translateChoice` is set to `false` when `<RadioButtonGroupInput>` is a child of `<ReferenceInput>`.

## Using In A ReferenceInput
## Fetching Choices

If you want to populate the `choices` attribute with a list of related records, you should decorate `<RadioButtonGroupInput>` with [`<ReferenceInput>`](./ReferenceInput.md), and leave the `choices` empty:
You can use [`useGetList`](./useGetList.md) to fetch choices. For example, to fetch a list of countries for a user profile:

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

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

<ReferenceInput label="Author" source="author_id" reference="authors">
<RadioButtonGroupInput />
</ReferenceInput>
The `isLoading` 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 `<RadioButtonGroupInput>` 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).

## Selecting a Foreign Key

If you use `<RadioButtonGroupInput>` to set a foreign key for a many-to-one or a one-to-one relationship, you'll have to [fetch choices](#fetching-choices), as explained in the previous section. You'll also have to fetch the record corresponding to the current value of the foreign key, as it may not be in the list of choices.

For example, if a `contact` has one `company` via the `company_id` foreign key, a contact form can let users select a company as follows:

```jsx
import { useGetList, useGetOne, RadioButtonGroupInput } from 'react-admin';
import { useWatch } from 'react-hook-form';

const CompanyInput = () => {
// fetch possible companies
const { data: choices, isLoading: isLoadingChoices } = useGetList('companies');
// 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 });
// 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]
: [];
return (
<RadioButtonGroupInput
label="Company"
source="company_id"
choices={choicesWithCurrentCompany}
optionText="name"
disabled={isLoading}
/>
);
}
```

In that case, `<RadioButtonGroupInput>` uses the [`recordRepresentation`](./Resource.md#recordrepresentation) to render each choice from the list of possible records. You can override this behavior by setting the `optionText` prop:
As this is a common task, react-admin provides a shortcut to do the same in a declarative way: [`<ReferenceInput>`](./ReferenceInput.md):

```jsx
import { RadioButtonGroupInput, ReferenceInput } from 'react-admin';
import { ReferenceInput, RadioButtonGroupInput } from 'react-admin';

const CompanyInput = () => (
<ReferenceInput reference="companies" source="company_id">
<RadioButtonGroupInput
label="Company"
source="company_id"
optionText="name"
/>
</ReferenceInput>
);
```

<ReferenceInput label="Author" source="author_id" reference="authors">
<RadioButtonGroupInput optionText="last_name" />
</ReferenceInput>
```
`<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,
- and renders its child component

When rendered as a child of `<ReferenceInput>`, `<RadioButtonGroupInput>` reads that `ChoiceContext` to populate its own `choices` and `isLoading` props.

In fact, you can simplify the code even further:

- `<ReferenceInput>` puts all its props inside the `ChoiceContext`, including `source`, so `<RadioButtonGroupInput>` doesn't need to repeat it.
- You can also put the `label` prop on the `<ReferenceInput>` rather than `<RadioButtonGroupInput>` so that it looks just like [`<ReferenceField>`](./ReferenceField.md) (for easier memorization).
- `<RadioButtonGroupInput>` uses the [`recordRepresentation`](./Resource.md#recordrepresentation) to determine how to represent the related choices. In the example above, the `companies` resource uses `name` as its `recordRepresentation`, so `<RadioButtonGroupInput>` will default to `optionText="name"`.

The code for the `<CompanyInput>` component can be reduced to:

```jsx
import { ReferenceInput, RadioButtonGroupInput } from 'react-admin';

const CompanyInput = () => (
<ReferenceInput reference="companies" source="company_id" label="Company">
<RadioButtonGroupInput />
</ReferenceInput>
);
```

This is the recommended approach for using `<RadioButtonGroupInput>` to select a foreign key. This not only signifies that the input is a `<RadioButtonGroupInput>` but also highlights its function in fetching choices from another resource, ultimately enhancing the code's readability.

**Tip**: `<ReferenceInput>` is much more powerful than the initial snippet. It optimizes and caches API calls, enables refetching of both API calls with a single command, and stores supplementary data in the `<ChoicesContext>`. `<ReferenceInput>` can provide choices to `<RadioButtonGroupInput>`, but also to [`<AutocompleteInput>`](./AutocompleteInput.md) and [`<SelectInput>`](./SelectInput.md). For further information, refer to [the `<ReferenceInput>` documentation](./ReferenceInput.md).
4 changes: 3 additions & 1 deletion docs/SelectArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ In that case, set the `translateChoice` prop to `false`.
<SelectArrayInput source="roles" choices={choices} translateChoice={false}/>
```

## Using in a ReferenceArrayInput
## Fetching Choices

If you want to populate the `choices` attribute with a list of related records, you should decorate `<SelectArrayInput>` with [`<ReferenceArrayInput>`](./ReferenceArrayInput.md), and leave the `choices` empty:

Expand Down Expand Up @@ -414,6 +414,8 @@ export const PostCreate = () => (

**Tip**: As it does not provide autocompletion, `<SelectArrayInput>` might not be suited when the reference resource has a lot of items.

Check [the `<ReferenceArrayInput>` documentation](./ReferenceArrayInput.md) for more details.

## Creating New Choices

The `<SelectArrayInput>` can allow users to create a new choice if either the `create` or `onCreate` prop is provided.
Expand Down
Loading

0 comments on commit 318cc33

Please sign in to comment.