Skip to content

Commit

Permalink
Merge pull request #9046 from marmelab/fix-autocompletearrayinput-gli…
Browse files Browse the repository at this point in the history
…tches

Update documentation about AutocompleteInput and AutocompleteArrayInput
  • Loading branch information
slax57 committed Jul 3, 2023
2 parents 4be368a + da6a09f commit 8e12cfd
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 26 deletions.
92 changes: 68 additions & 24 deletions docs/AutocompleteArrayInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,52 +310,77 @@ If a prompt is not enough, you can use [the `create` prop](#create) to render a

## `optionText`

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

```jsx
const choices = [
{ id: 'admin', label: 'Admin' },
{ id: 'u001', label: 'Editor' },
{ id: 'u002', label: 'Moderator' },
{ id: 'u003', label: 'Reviewer' },
];
<AutocompleteArrayInput source="roles" choices={choices} optionText="label" />
import { AutocompleteArrayInput } from 'react-admin';

<AutocompleteArrayInput
source="categories"
choices={[
{ id: 'tech', name: 'Tech' },
{ id: 'lifestyle', name: 'Lifestyle' },
{ id: 'people', name: 'People' },
]}
/>
// renders the following list of choices
// - Tech
// - Lifestyle
// - People
```

`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.
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:

```jsx
<ReferenceArrayInput source="tag_ids" reference="tags">
<AutocompleteArrayInput optionText="tag" />
</ReferenceArrayInput>
<AutocompleteArrayInput
source="categories"
optionText="label"
choices={[
{ id: 'tech', label: 'Tech' },
{ id: 'lifestyle', label: 'Lifestyle' },
{ id: 'people', label: 'People' },
]}
/>
```

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

```jsx
const choices = [
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];

// Note we declared the function outside the component to avoid rerenders
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;

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

`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.
**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).

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

`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).

```jsx
const choices = [
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];
import { AutocompleteArrayInput, ReferenceArrayInput } from 'react-admin';

const FullNameField = () => {
const record = useRecordContext();
return <span>{record.first_name} {record.last_name}</span>;
}
<ReferenceArrayInput label="Author" source="authors_ids" reference="authors">
<AutocompleteArrayInput />
</ReferenceArrayInput>
```

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.

But if you set the `optionText` prop, react-admin uses it instead of relying on `recordRepresentation`.

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

<AutocompleteArrayInput source="authors" choices={choices} optionText={<FullNameField />}/>
<ReferenceArrayInput label="Author" source="authors_ids" reference="authors">
<AutocompleteArrayInput optionText="last_name" />
</ReferenceArrayInput>
```

## `optionValue`
Expand Down Expand Up @@ -517,6 +542,7 @@ const OptionRenderer = () => {
</span>
);
};
const optionText = <OptionRenderer />;
const inputText = choice => `${choice.first_name} ${choice.last_name}`;
const matchSuggestion = (filter, choice) => {
return (
Expand All @@ -528,12 +554,30 @@ const matchSuggestion = (filter, choice) => {
<AutocompleteArrayInput
source="author_ids"
choices={choices}
optionText={<OptionRenderer />}
optionText={optionText}
inputText={inputText}
matchSuggestion={matchSuggestion}
/>
```

**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).

**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:

```jsx
const OptionRenderer = () => {
const record = useRecordContext();
return (
<span>
<img src={record.avatar} />
{record.first_name} {record.last_name}
</span>
);
};

const optionText = <OptionRenderer />;
```

## Creating New Choices

The `<AutocompleteArrayInput>` can allow users to create a new choice if either the `create` or `onCreate` prop is provided.
Expand Down
27 changes: 26 additions & 1 deletion docs/AutocompleteInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,10 +370,15 @@ const choices = [
{ id: 123, first_name: 'Leo', last_name: 'Tolstoi' },
{ id: 456, first_name: 'Jane', last_name: 'Austen' },
];

// Note we declared the function outside the component to avoid rerenders
const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`;

<AutocompleteInput source="author_id" choices={choices} optionText={optionRenderer} />
```

**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).

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

`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).
Expand Down Expand Up @@ -637,6 +642,8 @@ const OptionRenderer = () => {
</span>
);
};

const optionText = <OptionRenderer />;
const inputText = choice => `${choice.first_name} ${choice.last_name}`;
const matchSuggestion = (filter, choice) => {
return (
Expand All @@ -648,12 +655,30 @@ const matchSuggestion = (filter, choice) => {
<AutocompleteInput
source="author_id"
choices={choices}
optionText={<OptionRenderer />}
optionText={optionText}
inputText={inputText}
matchSuggestion={matchSuggestion}
/>
```

**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).

**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:

```jsx
const OptionRenderer = () => {
const record = useRecordContext();
return (
<span>
<img src={record.avatar} />
{record.first_name} {record.last_name}
</span>
);
};

const optionText = <OptionRenderer />;
```

## Creating New Choices

The `<AutocompleteInput>` can allow users to create a new choice if either the `create` or `onCreate` prop is provided.
Expand Down
5 changes: 4 additions & 1 deletion packages/ra-ui-materialui/src/input/AutocompleteInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
useTranslate,
warning,
useGetRecordRepresentation,
useEvent,
} from 'ra-core';
import {
SupportCreateSuggestionOptions,
Expand Down Expand Up @@ -151,7 +152,7 @@ export const AutocompleteInput = <
matchSuggestion,
margin,
fieldState: fieldStateOverride,
filterToQuery = DefaultFilterToQuery,
filterToQuery: filterToQueryProp = DefaultFilterToQuery,
formState: formStateOverride,
multiple = false,
noOptionsText,
Expand All @@ -175,6 +176,8 @@ export const AutocompleteInput = <
...rest
} = props;

const filterToQuery = useEvent(filterToQueryProp);

const {
allChoices,
isLoading,
Expand Down

0 comments on commit 8e12cfd

Please sign in to comment.