Skip to content

Commit 8e12cfd

Browse files
authored
Merge pull request #9046 from marmelab/fix-autocompletearrayinput-glitches
Update documentation about AutocompleteInput and AutocompleteArrayInput
2 parents 4be368a + da6a09f commit 8e12cfd

File tree

3 files changed

+98
-26
lines changed

3 files changed

+98
-26
lines changed

docs/AutocompleteArrayInput.md

+68-24
Original file line numberDiff line numberDiff line change
@@ -310,52 +310,77 @@ If a prompt is not enough, you can use [the `create` prop](#create) to render a
310310

311311
## `optionText`
312312

313-
You can customize the properties to use for the option name (instead of the default `name`) thanks to the `optionText` prop:
313+
By default, `<AutocompleteArrayInput>` uses the `name` property as the text content of each option.
314314

315315
```jsx
316-
const choices = [
317-
{ id: 'admin', label: 'Admin' },
318-
{ id: 'u001', label: 'Editor' },
319-
{ id: 'u002', label: 'Moderator' },
320-
{ id: 'u003', label: 'Reviewer' },
321-
];
322-
<AutocompleteArrayInput source="roles" choices={choices} optionText="label" />
316+
import { AutocompleteArrayInput } from 'react-admin';
317+
318+
<AutocompleteArrayInput
319+
source="categories"
320+
choices={[
321+
{ id: 'tech', name: 'Tech' },
322+
{ id: 'lifestyle', name: 'Lifestyle' },
323+
{ id: 'people', name: 'People' },
324+
]}
325+
/>
326+
// renders the following list of choices
327+
// - Tech
328+
// - Lifestyle
329+
// - People
323330
```
324331

325-
`optionText` is especially useful when the choices are records coming from a `<ReferenceArrayInput>` or a `<ReferenceManyToManyInput>`. By default, react-admin uses the [`recordRepresentation`](./Resource.md#recordrepresentation) function to display the record label. But if you set the `optionText` prop, react-admin will use it instead.
332+
If your `choices` don't have a `name` property, or if you want to use another property, you can use the `optionText` prop to specify which property to use:
326333

327334
```jsx
328-
<ReferenceArrayInput source="tag_ids" reference="tags">
329-
<AutocompleteArrayInput optionText="tag" />
330-
</ReferenceArrayInput>
335+
<AutocompleteArrayInput
336+
source="categories"
337+
optionText="label"
338+
choices={[
339+
{ id: 'tech', label: 'Tech' },
340+
{ id: 'lifestyle', label: 'Lifestyle' },
341+
{ id: 'people', label: 'People' },
342+
]}
343+
/>
331344
```
332345

333-
`optionText` also accepts a function, so you can shape the option text based on the entire choice object:
346+
`optionText` also accepts a function, so you can shape the option text at will:
334347

335348
```jsx
336349
const choices = [
337350
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
338351
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
339352
];
353+
354+
// Note we declared the function outside the component to avoid rerenders
340355
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
341356

342357
<AutocompleteArrayInput source="authors" choices={choices} optionText={optionRenderer} />
343358
```
344359

345-
`optionText` also accepts a React Element, that will be rendered inside a [`<RecordContext>`](./useRecordContext.md) using the related choice as the `record` prop. You can use Field components there.
360+
**Tip**: Make sure you provide a stable reference to the function passed as `optionText`. Either declare it outside the component render function or wrap it inside a [`useCallback`](https://react.dev/reference/react/useCallback).
361+
362+
`optionText` also accepts a React Element, that will be rendered inside a [`<RecordContext>`](./useRecordContext.md) using the related choice as the `record` prop. You can use Field components there. However, using an element as `optionText` implies that you also set two more props, `inputText` and `matchSuggestion`. See [Using A Custom Element For Options](#using-a-custom-element-for-options) for more details.
363+
364+
`optionText` is also useful when the choices are records [fetched from another resource](#fetching-choices), and `<AutocompleteArrayInput>` is a child of a [`<ReferenceArrayInput>`](./ReferenceArrayInput.md).
346365

347366
```jsx
348-
const choices = [
349-
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
350-
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
351-
];
367+
import { AutocompleteArrayInput, ReferenceArrayInput } from 'react-admin';
352368

353-
const FullNameField = () => {
354-
const record = useRecordContext();
355-
return <span>{record.first_name} {record.last_name}</span>;
356-
}
369+
<ReferenceArrayInput label="Author" source="authors_ids" reference="authors">
370+
<AutocompleteArrayInput />
371+
</ReferenceArrayInput>
372+
```
373+
374+
In that case, react-admin uses the [`recordRepresentation`](./Resource.md#recordrepresentation) of the related resource to display the record label. In the example above, `<AutocompleteArrayInput>` uses the resource representation of the `authors` resource, which is the `name` property.
375+
376+
But if you set the `optionText` prop, react-admin uses it instead of relying on `recordRepresentation`.
377+
378+
```jsx
379+
import { AutocompleteArrayInput, ReferenceArrayInput } from 'react-admin';
357380

358-
<AutocompleteArrayInput source="authors" choices={choices} optionText={<FullNameField />}/>
381+
<ReferenceArrayInput label="Author" source="authors_ids" reference="authors">
382+
<AutocompleteArrayInput optionText="last_name" />
383+
</ReferenceArrayInput>
359384
```
360385

361386
## `optionValue`
@@ -517,6 +542,7 @@ const OptionRenderer = () => {
517542
</span>
518543
);
519544
};
545+
const optionText = <OptionRenderer />;
520546
const inputText = choice => `${choice.first_name} ${choice.last_name}`;
521547
const matchSuggestion = (filter, choice) => {
522548
return (
@@ -528,12 +554,30 @@ const matchSuggestion = (filter, choice) => {
528554
<AutocompleteArrayInput
529555
source="author_ids"
530556
choices={choices}
531-
optionText={<OptionRenderer />}
557+
optionText={optionText}
532558
inputText={inputText}
533559
matchSuggestion={matchSuggestion}
534560
/>
535561
```
536562

563+
**Tip**: Make sure you pass stable references to the functions passed to the `inputText` and `matchSuggestion` by either declaring them outside the component render function or by wrapping them in a [`useCallback`](https://react.dev/reference/react/useCallback).
564+
565+
**Tip**: Make sure you pass a stable reference to the element passed to the `optionText` prop by calling it outside the component render function like so:
566+
567+
```jsx
568+
const OptionRenderer = () => {
569+
const record = useRecordContext();
570+
return (
571+
<span>
572+
<img src={record.avatar} />
573+
{record.first_name} {record.last_name}
574+
</span>
575+
);
576+
};
577+
578+
const optionText = <OptionRenderer />;
579+
```
580+
537581
## Creating New Choices
538582

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

docs/AutocompleteInput.md

+26-1
Original file line numberDiff line numberDiff line change
@@ -370,10 +370,15 @@ const choices = [
370370
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
371371
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
372372
];
373+
374+
// Note we declared the function outside the component to avoid rerenders
373375
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;
376+
374377
<AutocompleteInput source="author_id" choices={choices} optionText={optionRenderer} />
375378
```
376379

380+
**Tip**: Make sure you provide a stable reference to the function passed as `optionText`. Either declare it outside the component render function or wrap it inside a [`useCallback`](https://react.dev/reference/react/useCallback).
381+
377382
`optionText` also accepts a React Element, that will be rendered inside a [`<RecordContext>`](./useRecordContext.md) using the related choice as the `record` prop. You can use Field components there. However, using an element as `optionText` implies that you also set two more props, `inputText` and `matchSuggestion`. See [Using A Custom Element For Options](#using-a-custom-element-for-options) for more details.
378383

379384
`optionText` is also useful when the choices are records [fetched from another resource](#fetching-choices), and `<AutocompleteInput>` is a child of a [`<ReferenceInput>`](./ReferenceInput.md).
@@ -637,6 +642,8 @@ const OptionRenderer = () => {
637642
</span>
638643
);
639644
};
645+
646+
const optionText = <OptionRenderer />;
640647
const inputText = choice => `${choice.first_name} ${choice.last_name}`;
641648
const matchSuggestion = (filter, choice) => {
642649
return (
@@ -648,12 +655,30 @@ const matchSuggestion = (filter, choice) => {
648655
<AutocompleteInput
649656
source="author_id"
650657
choices={choices}
651-
optionText={<OptionRenderer />}
658+
optionText={optionText}
652659
inputText={inputText}
653660
matchSuggestion={matchSuggestion}
654661
/>
655662
```
656663

664+
**Tip**: Make sure you pass stable references to the functions passed to the `inputText` and `matchSuggestion` by either declaring them outside the component render function or by wrapping them in a [`useCallback`](https://react.dev/reference/react/useCallback).
665+
666+
**Tip**: Make sure you pass a stable reference to the element passed to the `optionText` prop by calling it outside the component render function like so:
667+
668+
```jsx
669+
const OptionRenderer = () => {
670+
const record = useRecordContext();
671+
return (
672+
<span>
673+
<img src={record.avatar} />
674+
{record.first_name} {record.last_name}
675+
</span>
676+
);
677+
};
678+
679+
const optionText = <OptionRenderer />;
680+
```
681+
657682
## Creating New Choices
658683

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

packages/ra-ui-materialui/src/input/AutocompleteInput.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
useTranslate,
3434
warning,
3535
useGetRecordRepresentation,
36+
useEvent,
3637
} from 'ra-core';
3738
import {
3839
SupportCreateSuggestionOptions,
@@ -151,7 +152,7 @@ export const AutocompleteInput = <
151152
matchSuggestion,
152153
margin,
153154
fieldState: fieldStateOverride,
154-
filterToQuery = DefaultFilterToQuery,
155+
filterToQuery: filterToQueryProp = DefaultFilterToQuery,
155156
formState: formStateOverride,
156157
multiple = false,
157158
noOptionsText,
@@ -175,6 +176,8 @@ export const AutocompleteInput = <
175176
...rest
176177
} = props;
177178

179+
const filterToQuery = useEvent(filterToQueryProp);
180+
178181
const {
179182
allChoices,
180183
isLoading,

0 commit comments

Comments
 (0)