From 6110ec36049640bc62b15de72810cb6391f1e155 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:37:51 +0200 Subject: [PATCH 1/4] Memoize AutocompleteInput function props where possible --- packages/ra-ui-materialui/src/input/AutocompleteInput.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx index 623315f9a7a..d8a2cc9b4d2 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx @@ -33,6 +33,7 @@ import { useTranslate, warning, useGetRecordRepresentation, + useEvent, } from 'ra-core'; import { SupportCreateSuggestionOptions, @@ -151,13 +152,13 @@ export const AutocompleteInput = < matchSuggestion, margin, fieldState: fieldStateOverride, - filterToQuery = DefaultFilterToQuery, + filterToQuery: filterToQueryProp = DefaultFilterToQuery, formState: formStateOverride, multiple = false, noOptionsText, onBlur, onChange, - onCreate, + onCreate: onCreateProp, openText = 'ra.action.open', optionText, optionValue, @@ -175,6 +176,9 @@ export const AutocompleteInput = < ...rest } = props; + const filterToQuery = useEvent(filterToQueryProp); + const onCreate = useEvent(onCreateProp); + const { allChoices, isLoading, From 3b3d65f2059aee636e91a78aab7b81b6be5ec5b3 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Mon, 26 Jun 2023 11:38:16 +0200 Subject: [PATCH 2/4] Update documentation about AutocompleteInput and AutocompleteArrayInput --- docs/AutocompleteArrayInput.md | 53 +++++++++++++++++++++++++++------- docs/AutocompleteInput.md | 27 ++++++++++++++++- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/docs/AutocompleteArrayInput.md b/docs/AutocompleteArrayInput.md index 54a05903a35..e29ebddbf70 100644 --- a/docs/AutocompleteArrayInput.md +++ b/docs/AutocompleteArrayInput.md @@ -337,25 +337,37 @@ 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}`; ``` -`optionText` also accepts a React Element, that will be rendered inside a [``](./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 [``](./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 `` is a child of a [``](./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 {record.first_name} {record.last_name}; -} + + + +``` + +In that case, react-admin uses the [`recordRepresentation`](./Resource.md#recordrepresentation) of the related resource to display the record label. In the example above, `` 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'; -}/> + + + ``` ## `optionValue` @@ -517,6 +529,7 @@ const OptionRenderer = () => { ); }; +const optionText = ; const inputText = choice => `${choice.first_name} ${choice.last_name}`; const matchSuggestion = (filter, choice) => { return ( @@ -528,12 +541,30 @@ const matchSuggestion = (filter, choice) => { } + 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 ( + + + {record.first_name} {record.last_name} + + ); +}; + +const optionText = ; +``` + ## Creating New Choices The `` can allow users to create a new choice if either the `create` or `onCreate` prop is provided. diff --git a/docs/AutocompleteInput.md b/docs/AutocompleteInput.md index d48ddc650ed..49cfd6bafa4 100644 --- a/docs/AutocompleteInput.md +++ b/docs/AutocompleteInput.md @@ -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}`; + ``` +**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 [``](./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 `` is a child of a [``](./ReferenceInput.md). @@ -637,6 +642,8 @@ const OptionRenderer = () => { ); }; + +const optionText = ; const inputText = choice => `${choice.first_name} ${choice.last_name}`; const matchSuggestion = (filter, choice) => { return ( @@ -648,12 +655,30 @@ const matchSuggestion = (filter, choice) => { } + 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 ( + + + {record.first_name} {record.last_name} + + ); +}; + +const optionText = ; +``` + ## Creating New Choices The `` can allow users to create a new choice if either the `create` or `onCreate` prop is provided. From 437e5b40826bf47864a8df41ac8b25a1089dcbd7 Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:40:25 +0200 Subject: [PATCH 3/4] Fix onCreate cannot be memoized --- packages/ra-ui-materialui/src/input/AutocompleteInput.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx index d8a2cc9b4d2..7496f2d8522 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.tsx @@ -158,7 +158,7 @@ export const AutocompleteInput = < noOptionsText, onBlur, onChange, - onCreate: onCreateProp, + onCreate, openText = 'ra.action.open', optionText, optionValue, @@ -177,7 +177,6 @@ export const AutocompleteInput = < } = props; const filterToQuery = useEvent(filterToQueryProp); - const onCreate = useEvent(onCreateProp); const { allChoices, From da6a09f7ccb3ac0d02ac4c60b93df9f7c11bb79c Mon Sep 17 00:00:00 2001 From: Gildas Garcia <1122076+djhi@users.noreply.github.com> Date: Mon, 3 Jul 2023 14:46:58 +0200 Subject: [PATCH 4/4] Adapt AutocompleteInput documentation to AutocompleteArrayInput --- docs/AutocompleteArrayInput.md | 43 ++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/docs/AutocompleteArrayInput.md b/docs/AutocompleteArrayInput.md index e29ebddbf70..459e2d0e19c 100644 --- a/docs/AutocompleteArrayInput.md +++ b/docs/AutocompleteArrayInput.md @@ -310,27 +310,40 @@ 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, `` 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' }, -]; - +import { AutocompleteArrayInput } from 'react-admin'; + + +// renders the following list of choices +// - Tech +// - Lifestyle +// - People ``` -`optionText` is especially useful when the choices are records coming from a `` or a ``. 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 - - - + ``` -`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 = [ @@ -353,7 +366,7 @@ const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`; ```jsx import { AutocompleteArrayInput, ReferenceArrayInput } from 'react-admin'; - + ``` @@ -365,7 +378,7 @@ But if you set the `optionText` prop, react-admin uses it instead of relying on ```jsx import { AutocompleteArrayInput, ReferenceArrayInput } from 'react-admin'; - + ```