Skip to content

Commit 318cc33

Browse files
authored
Merge pull request #8821 from marmelab/doc-referenceinput
[Doc] Improve usage examples for `<ReferenceInput>` and `<ReferenceArrayInput>`
2 parents 9c997c1 + 79d0c35 commit 318cc33

12 files changed

+564
-86
lines changed

docs/AutocompleteArrayInput.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ In that case, set the `translateChoice` prop to `false`.
436436

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

439-
## Using In A ReferenceArrayInput
439+
## Fetching Choices
440440

441441
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:
442442

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

451+
Check [the `<ReferenceArrayInput>` documentation](./ReferenceArrayInput.md) for more details.
452+
451453
## Working With Object Values
452454

453455
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.

docs/AutocompleteInput.md

+175-31
Large diffs are not rendered by default.

docs/CheckboxGroupInput.md

+14
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,17 @@ However, in some cases (e.g. inside a `<ReferenceArrayInput>`), you may not want
246246
```jsx
247247
<CheckboxGroupInput source="roles" choices={choices} translateChoice={false}/>
248248
```
249+
250+
## Fetching Choices
251+
252+
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:
253+
254+
```jsx
255+
import { AutocompleteArrayInput, ReferenceArrayInput } from 'react-admin';
256+
257+
<ReferenceArrayInput label="Tags" reference="tags" source="tags">
258+
<CheckboxGroupInput />
259+
</ReferenceArrayInput>
260+
```
261+
262+
Check [the `<ReferenceArrayInput>` documentation](./ReferenceArrayInput.md) for more details.

docs/RadioButtonGroupInput.md

+124-15
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ The form value for the source must be the selected value, e.g.
5050

5151
| Prop | Required | Type | Default | Description |
5252
| ----------------- | -------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
53-
| `choices` | Optional | `Object[]` | - | List of items to show as options. Required unless inside a ReferenceInput. |
53+
| `choices` | Optional | `Object[]` | - | List of items to show as options. Required unless inside a ReferenceInput. |
54+
| `isLoading` | Optional | `boolean` | `false` | If `true`, the component will display a loading indicator. |
5455
| `options` | Optional | `Object` | - | Props to pass to the underlying `<RadioButtonGroup>` element |
5556
| `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}`) |
5657
| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value |
@@ -108,7 +109,7 @@ If you need to *fetch* the options from another resource, you're actually editin
108109
</ReferenceInput>
109110
```
110111

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

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

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

124+
## `isLoading`
125+
126+
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.
127+
128+
```jsx
129+
import { useGetList, RadioButtonGroupInput } from 'react-admin';
130+
131+
const UserCountry = () => {
132+
const { data, isLoading } = useGetList('countries');
133+
// data is an array of { id: 123, code: 'FR', name: 'France' }
134+
return (
135+
<RadioButtonGroupInput
136+
source="country"
137+
choices={data}
138+
optionText="name"
139+
optionValue="code"
140+
isLoading={isLoading}
141+
/>
142+
);
143+
}
144+
```
145+
123146
## `options`
124147

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

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

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

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

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

250-
## Using In A ReferenceInput
273+
## Fetching Choices
251274

252-
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:
275+
You can use [`useGetList`](./useGetList.md) to fetch choices. For example, to fetch a list of countries for a user profile:
253276

254277
```jsx
255-
import { RadioButtonGroupInput, ReferenceInput } from 'react-admin';
278+
import { useGetList, RadioButtonGroupInput } from 'react-admin';
279+
280+
const CountryInput = () => {
281+
const { data, isLoading } = useGetList('countries');
282+
// data is an array of { id: 123, code: 'FR', name: 'France' }
283+
return (
284+
<RadioButtonGroupInput
285+
source="country"
286+
choices={data}
287+
optionText="name"
288+
optionValue="code"
289+
isLoading={isLoading}
290+
/>
291+
);
292+
}
293+
```
256294

257-
<ReferenceInput label="Author" source="author_id" reference="authors">
258-
<RadioButtonGroupInput />
259-
</ReferenceInput>
295+
The `isLoading` prop is used to display a loading indicator while the data is being fetched.
296+
297+
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).
298+
299+
## Selecting a Foreign Key
300+
301+
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.
302+
303+
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:
304+
305+
```jsx
306+
import { useGetList, useGetOne, RadioButtonGroupInput } from 'react-admin';
307+
import { useWatch } from 'react-hook-form';
308+
309+
const CompanyInput = () => {
310+
// fetch possible companies
311+
const { data: choices, isLoading: isLoadingChoices } = useGetList('companies');
312+
// companies are like { id: 123, name: 'Acme' }
313+
// get the current value of the foreign key
314+
const companyId = useWatch({ name: 'company_id'})
315+
// fetch the current company
316+
const { data: currentCompany, isLoading: isLoadingCurrentCompany } = useGetOne('companies', { id: companyId });
317+
// if the current company is not in the list of possible companies, add it
318+
const choicesWithCurrentCompany = choices
319+
? choices.find(choice => choice.id === companyId)
320+
? choices
321+
: [...choices, currentCompany]
322+
: [];
323+
return (
324+
<RadioButtonGroupInput
325+
label="Company"
326+
source="company_id"
327+
choices={choicesWithCurrentCompany}
328+
optionText="name"
329+
disabled={isLoading}
330+
/>
331+
);
332+
}
260333
```
261334

262-
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:
335+
As this is a common task, react-admin provides a shortcut to do the same in a declarative way: [`<ReferenceInput>`](./ReferenceInput.md):
263336

264337
```jsx
265-
import { RadioButtonGroupInput, ReferenceInput } from 'react-admin';
338+
import { ReferenceInput, RadioButtonGroupInput } from 'react-admin';
339+
340+
const CompanyInput = () => (
341+
<ReferenceInput reference="companies" source="company_id">
342+
<RadioButtonGroupInput
343+
label="Company"
344+
source="company_id"
345+
optionText="name"
346+
/>
347+
</ReferenceInput>
348+
);
349+
```
266350

267-
<ReferenceInput label="Author" source="author_id" reference="authors">
268-
<RadioButtonGroupInput optionText="last_name" />
269-
</ReferenceInput>
270-
```
351+
`<ReferenceInput>` is a headless component that:
352+
353+
- fetches a list of records with `dataProvider.getList()` and `dataProvider.getOne()`, using the `reference` prop for the resource,
354+
- puts the result of the fetch in the `ChoiceContext` as the `choices` prop, as well as the `isLoading` state,
355+
- and renders its child component
356+
357+
When rendered as a child of `<ReferenceInput>`, `<RadioButtonGroupInput>` reads that `ChoiceContext` to populate its own `choices` and `isLoading` props.
358+
359+
In fact, you can simplify the code even further:
360+
361+
- `<ReferenceInput>` puts all its props inside the `ChoiceContext`, including `source`, so `<RadioButtonGroupInput>` doesn't need to repeat it.
362+
- 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).
363+
- `<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"`.
364+
365+
The code for the `<CompanyInput>` component can be reduced to:
366+
367+
```jsx
368+
import { ReferenceInput, RadioButtonGroupInput } from 'react-admin';
369+
370+
const CompanyInput = () => (
371+
<ReferenceInput reference="companies" source="company_id" label="Company">
372+
<RadioButtonGroupInput />
373+
</ReferenceInput>
374+
);
375+
```
376+
377+
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.
378+
379+
**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).

docs/SelectArrayInput.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ In that case, set the `translateChoice` prop to `false`.
383383
<SelectArrayInput source="roles" choices={choices} translateChoice={false}/>
384384
```
385385

386-
## Using in a ReferenceArrayInput
386+
## Fetching Choices
387387

388388
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:
389389

@@ -414,6 +414,8 @@ export const PostCreate = () => (
414414

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

417+
Check [the `<ReferenceArrayInput>` documentation](./ReferenceArrayInput.md) for more details.
418+
417419
## Creating New Choices
418420

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

0 commit comments

Comments
 (0)