diff --git a/docs/AutocompleteInput.md b/docs/AutocompleteInput.md index e47beb02a1b..897c844dd44 100644 --- a/docs/AutocompleteInput.md +++ b/docs/AutocompleteInput.md @@ -6,54 +6,69 @@ title: "The AutocompleteInput Component" # `` To let users choose a value in a list using a dropdown with autocompletion, use ``. -It renders using MUI [Autocomplete](https://mui.com/components/autocomplete/). +It renders using [MUI's ``](https://mui.com/components/autocomplete/). ![AutocompleteInput](./img/autocomplete-input.gif) -Set the `choices` attribute to determine the options list. The value must be an array of objects exposing the `id` and `name` for each choice. +This input allows editing record fields that are scalar values, e.g. `123`, `'admin'`, etc. + +## Usage + +In addition to the `source`, `` requires one prop: the `choices` listing the possible values. ```jsx import { AutocompleteInput } from 'react-admin'; ``` -**Tip**: If you want to populate the `choices` attribute with a list of related records, you should decorate `` with [``](./ReferenceInput.md), and leave the `choices` empty: +By default, the possible choices are built from the `choices` prop, using: + - the `id` field as the option value, + - the `name` field as the option text -```jsx -import { AutocompleteInput, ReferenceInput } from 'react-admin'; +The form value for the source must be the selected value, e.g. - - - +```js +{ + id: 123, + title: 'Lorem Ipsum', + category: 'lifestyle', +} ``` -**Tip**: `` is a stateless component, so it only allows to *filter* the list of choices, not to *extend* it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](./ReferenceInput.md) doesn't cover your need), you'll have to [write your own Input component](./Inputs.md#writing-your-own-input-component) based on MUI `` component. - -## Properties - -| Prop | Required | Type | Default | Description | -|---------------------------|----------|-----------------------------------------------|-----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `choices` | Optional | `Object[]` | `-` | List of items to autosuggest. Required if not inside a ReferenceInput. | -| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice | -| `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty | -| `createItemLabel` | Optional | `string` | `ra.action.create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty | -| `debounce` | Optional | `number` | `250` | The delay to wait before calling the setFilter function injected when used in a ReferenceInput. | -| `emptyText` | Optional | `string` | `''` | The text to use for the empty element | -| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element | -| `inputText` | Optional | `Function` | `-` | Required if `optionText` is a custom Component, this function must return the text displayed for the current selection. | -| `matchSuggestion` | Optional | `Function` | `-` | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` | -| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. | -| `optionText` | Optional | `string` | `Function` | `Component` | `name` | Field name of record to display in the suggestion item or function which accepts the correct record as argument (`(record)=> {string}`) | -| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value | -| `filterToQuery` | Optional | `string` => `Object` | `searchText => ({ q: [searchText] })` | How to transform the searchText into a parameter for the data provider | -| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically set up when using `ReferenceInput`. | -| `shouldRenderSuggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. Use this when working with large collections of data to improve performance and user experience. This function is passed into the underlying `MUI Autocomplete` component. Ex.`(value) => value.trim().length > 2` | -| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list | +**Tip**: React-admin includes other components to edit such values: + + - [``](./SelectInput.md) renders a dropdown + - [``](./RadioButtonGroupInput.md) renders a list of radio buttons + +**Tip**: If you need to let users select more than one item in the list, check out the [``](./AutocompleteArrayInput.md) component. + +**Tip**: `` is a stateless component, so it only allows to *filter* the list of choices, not to *extend* it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](./ReferenceInput.md) doesn't cover your need), you'll have to [write your own Input component](./Inputs.md#writing-your-own-input-component) based on MUI `` component. + +## Props + +| Prop | Required | Type | Default | Description | +|--------------------------- |----------|---------------------- |------------------------- |-------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `choices` | Optional | `Object[]` | `-` | List of items to autosuggest. Required if not inside a ReferenceInput. | +| `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice | +| `createLabel` | Optional | `string` | `ra.action .create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty | +| `createItemLabel` | Optional | `string` | `ra.action .create_item` | The label for the menu item allowing users to create a new choice. Used when the filter is not empty | +| `debounce` | Optional | `number` | `250` | The delay to wait before calling the setFilter function injected when used in a ReferenceInput. | +| `emptyText` | Optional | `string` | `''` | The text to use for the empty element | +| `emptyValue` | Optional | `any` | `''` | The value to use for the empty element | +| `filterToQuery` | Optional | `string` => `Object` | `q => ({ q })` | How to transform the searchText into a parameter for the data provider | +| `inputText` | Optional | `Function` | `-` | Required if `optionText` is a custom Component, this function must return the text displayed for the current selection. | +| `matchSuggestion` | Optional | `Function` | `-` | Required if `optionText` is a React element. Function returning a boolean indicating whether a choice matches the filter. `(filter, choice) => boolean` | +| `onCreate` | Optional | `Function` | `-` | A function called with the current filter value when users choose to create a new choice. | +| `optionText` | Optional | `string` | `Function` | `Component` | `name` | Field name of record to display in the suggestion item or function which accepts the correct record as argument (`(record)=> {string}`) | +| `optionValue` | Optional | `string` | `id` | Field name of record containing the value to use as input value | +| `setFilter` | Optional | `Function` | `null` | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically set up when using `ReferenceInput`. | +| `shouldRender Suggestions` | Optional | `Function` | `() => true` | A function that returns a `boolean` to determine whether or not suggestions are rendered. | +| `suggestionLimit` | Optional | `number` | `null` | Limits the numbers of suggestions that are shown in the dropdown list | `` also accepts the [common input props](./Inputs.md#common-input-props). @@ -63,11 +78,10 @@ An array of objects that represents the possible suggestions. The objects must h ```jsx const choices = [ - { id: 'programming', name: 'Programming' }, + { id: 'tech', name: 'Tech' }, { id: 'lifestyle', name: 'Lifestyle' }, - { id: 'photography', name: 'Photography' }, + { id: 'people', name: 'People' }, ]; - ``` @@ -75,20 +89,32 @@ If the choices have different keys, you can use [`optionText`](#optiontext) and ```jsx const choices = [ - { name: 'programming', label: 'Programming' }, - { name: 'lifestyle', label: 'Lifestyle' }, - { name: 'photography', label: 'Photography' }, + { _id: 'tech', label: 'Tech' }, + { _id: 'lifestyle', label: 'Lifestyle' }, + { _id: 'people', label: 'People' }, ]; ``` -When used inside a ``, `` doesn't need a `choices` prop. Instead, it will use the records fetched by `` as choices, via the `ChoicesContext`. +The choices are translated by default, so you can use translation identifiers as choices: + +```jsx +const choices = [ + { id: 'tech', name: 'myroot.categories.tech' }, + { id: 'lifestyle', name: 'myroot.categories.lifestyle' }, + { id: 'people', name: 'myroot.categories.people' }, +]; +``` + +You can opt-out of this translation by setting [the `translateChoice` prop](#translatechoice) to `false`. + +If you need to *fetch* the options from another resource, you're actually editing a many-to-one or a one-to-one relationship. In this case, wrap the `` in a [``](./ReferenceInput.md). You don't need to specify the `choices` prop - the parent component injects it based on the possible values of the related resource. ```jsx @@ -98,6 +124,101 @@ When used inside a ``, `` doesn't need a `cho See [Using in a `ReferenceInput>`](#using-in-a-referenceinput) 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: + +```jsx +const possibleValues = ['tech', 'lifestyle', 'people']; +const ucfirst = name => name.charAt(0).toUpperCase() + name.slice(1); +const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) })); + + +``` + +## `create` + +To allow users to add new options, pass a React element as the `create` prop. `` will then render a menu item at the bottom of the list, which will render the passed element when clicked. + +{% raw %} +```jsx +import { CreateCategory } from './CreateCategory'; + +const PostCreate = () => ( + + + + + } /> + + + +); + +// in ./CreateCategory.js +import React from 'react'; +import { useCreate, useCreateSuggestionContext } from 'react-admin'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + TextField, +} from '@mui/material'; + +const CreateCategory = () => { + const { filter, onCancel, onCreate } = useCreateSuggestionContext(); + const [create] = useCreate(); + const [value, setValue] = React.useState(filter || ''); + + const handleSubmit = event => { + event.preventDefault(); + create( + 'categories', + { data: { title: value } }, + { + onSuccess: (data) => { + setValue(''); + onCreate(data); + }, + } + ); + }; + + return ( + +
+ + setValue(event.target.value)} + autoFocus + /> + + + + + +
+
+ ); +}; +``` +{% endraw %} + +If you just need to ask users for a single string to create the new option, you can use [the `onCreate` prop](#oncreate) instead. + +## `debounce` + +When used inside a [``](./ReferenceInput.md), `` will call `dataProvider.getList()` with the current input value as filter after a delay of 250ms. This is to avoid calling the API too often while users are typing their query. + +This delay can be customized by setting the `debounce` prop. + +```jsx + + + +``` + ## `emptyText` If the input isn't required (using `validate={required()}`), users can select an empty choice with an empty text `''` as label. @@ -105,14 +226,7 @@ If the input isn't required (using `validate={required()}`), users can select an You can override that label with the `emptyText` prop. ```jsx - + ``` The `emptyText` prop accepts either a string or a React Element. @@ -120,14 +234,7 @@ The `emptyText` prop accepts either a string or a React Element. And if you want to hide that empty choice, make the input required. ```jsx - + ``` ## `emptyValue` @@ -137,30 +244,84 @@ If the input isn't required (using `validate={required()}`), users can select an You can override this value with the `emptyValue` prop. ```jsx - + ``` **Tip**: While you can set `emptyValue` to a non-string value (e.g. `0`), you cannot use `null` or `undefined`, as it would turn the `` into an [uncontrolled component](https://reactjs.org/docs/uncontrolled-components.html). If you need the empty choice to be stored as `null` or `undefined`, use [the `parse` prop](./Inputs.md#parse) to convert the default empty value ('') to `null` or `undefined`, or use [the `sanitizeEmptyValues` prop](./SimpleForm.md#sanitizeemptyvalues) on the Form component. +## `filterToQuery` + +When used inside a [``](./ReferenceInput.md), whenever users type a string in the autocomplete input, `` calls `dataProvider.getList()` using the string as filter, to return a filtered list of possible options from the reference resource. This filter is built using the `filterToQuery` prop. + +By default, the filter is built using the `q` parameter. This means that if the user types the string 'lorem', the filter will be `{ q: 'lorem' }`. + +You can customize the filter by setting the `filterToQuery` prop. It should be a function that returns a filter object. + +```jsx +const filterToQuery = searchText => ({ name_ilike: `%${searchText}%` }); + + + + +``` + +## `onCreate` + +Use the `onCreate` prop to allow users to create new options on-the-fly. Its value must be a function. This lets you render a `prompt` to ask users about the new value. You can return either the new choice directly or a Promise resolving to the new choice. + +{% raw %} +```js +import { AutocompleteInput, Create, SimpleForm, TextInput } from 'react-admin'; + +const PostCreate = () => { + const categories = [ + { name: 'Tech', id: 'tech' }, + { name: 'Lifestyle', id: 'lifestyle' }, + ]; + return ( + + + + { + const newCategoryName = prompt('Enter a new category'); + const newCategory = { id: newCategoryName.toLowerCase(), name: newCategoryName }; + categories.push(newCategory); + return newCategory; + }} + source="category" + choices={categories} + /> + + + ); +} +``` +{% endraw %} + +If a prompt is not enough, you can use [the `create` prop](#create) to render a custom component instead. + ## `optionText` -You can customize the choice field to use for the option name, thanks to the `optionText` attribute: +You can customize the property to use for the option name (instead of the default `name`) thanks to the `optionText` prop: ```jsx -// no 'name' field in the choices const choices = [ - { id: 123, full_name: 'Leo Tolstoi', sex: 'M' }, - { id: 456, full_name: 'Jane Austen', sex: 'F' }, + { id: 'tech', label: 'Tech' }, + { id: 'lifestyle', label: 'Lifestyle' }, + { id: 'people', label: 'People' }, ]; - + +``` + +`optionText` is particularly useful when the choices are records fetched from another resource, and `` is a child of a [``](./ReferenceInput.md). 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. + +```jsx +import { AutocompleteInput, ReferenceInput } from 'react-admin'; + + + + ``` `optionText` also accepts a function, so you can shape the option text at will: @@ -174,49 +335,71 @@ const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`; ``` -`optionText` also accepts a custom Component. However, as the underlying Autocomplete component requires that the current selection is a string, if you opt for a Component, you must pass a function as the `inputText` prop. This function should return a text representation of the current selection: +`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. + +## `optionValue` + +You can customize the property to use for the option value (instead of the default `id`) thanks to the `optionValue` prop: ```jsx const choices = [ - { id: 123, first_name: 'Leo', last_name: 'Tolstoi', avatar:'/pengouin' }, - { id: 456, first_name: 'Jane', last_name: 'Austen', avatar:'/panda' }, + { _id: 'tech', name: 'Tech' }, + { _id: 'lifestyle', name: 'Lifestyle' }, + { _id: 'people', name: 'People' }, ]; -const OptionRenderer = choice => ( - - - {choice.first_name} {choice.last_name} - -); -const inputText = choice => `${choice.first_name} ${choice.last_name}`; -const matchSuggestion = (filter, choice) => { - return ( - choice.first_name.toLowerCase().includes(filter.toLowerCase()) - || choice.last_name.toLowerCase().includes(filter.toLowerCase()) - ); -}; - } - inputText={inputText} - matchSuggestion={matchSuggestion} + optionValue="_id" /> ``` -## `optionValue` +## `shouldRenderSuggestions` -You can customize the choice field to use for the option value, thanks to the `optionValue` attribute: +When dealing with a large amount of `choices` you may need to limit the number of suggestions that are rendered in order to maintain acceptable performance. `shouldRenderSuggestions` is an optional prop that allows you to set conditions on when to render suggestions. An easy way to improve performance would be to skip rendering until the user has entered 2 or 3 characters in the search box. This lowers the result set significantly and might be all you need (depending on your data set). ```jsx -// no 'id' field in the choices -const choices = [ - { _id: 123, name: 'Leo Tolstoi' }, - { _id: 456, name: 'Jane Austen' }, -]; - + { return val.trim().length > 2 }} +/> +``` + +This prop is passed to the underlying `react-autosuggest` component and is documented [in their respository](https://github.com/moroshko/react-autosuggest#should-render-suggestions-prop). + +## `suggestionLimit` + +The `choices` prop can be very large, and rendering all of them would be very slow. To limit the number of suggestions displayed at any time, set the `suggestionLimit` prop: + +```jsx + ``` +If you're using `` inside a [``](./ReferenceInput.md), limit the number of choices returned by the API instead, using the `perPage` prop of the ``. + +```jsx + + + +``` + +## `sx`: CSS API + +The `` component accepts the usual `className` prop. You can also override many styles of the inner components thanks to the `sx` property (as most MUI components, see their [documentation about it](https://mui.com/customization/how-to-customize/#overriding-nested-component-styles)). This property accepts the following subclasses: + +| Rule name | Description | +|------------------------------|-------------------------------------------------| +| `& .RaSelectInput-textField` | Applied to the underlying `TextField` component | + +To override the style of all instances of `` using the [MUI style overrides](https://mui.com/customization/globals/#css), use the `RaAutocompleteInput` key. + +Refer to the [MUI `` component](https://mui.com/components/autocomplete/) to know its CSS API. + ## `translateChoice` The choices are translated by default, so you can use translation identifiers as choices: @@ -235,15 +418,6 @@ In that case, set the `translateChoice` prop to `false`. ``` -## `shouldRenderSuggestions` - -When dealing with a large amount of `choices` you may need to limit the number of suggestions that are rendered in order to maintain usable performance. The `shouldRenderSuggestions` is an optional prop that allows you to set conditions on when to render suggestions. An easy way to improve performance would be to skip rendering until the user has entered 2 or 3 characters in the search box. This lowers the result set significantly and might be all you need (depending on your data set). -Ex. ` { return val.trim().length > 2 }} />` would not render any suggestions until the 3rd character has been entered. This prop is passed to the underlying `react-autosuggest` component and is documented [here](https://github.com/moroshko/react-autosuggest#should-render-suggestions-prop). - -## `sx`: CSS API - -This component doesn't apply any custom styles on top of [MUI `` component](https://mui.com/components/autocomplete/). Refer to their documentation to know its CSS API. - ## Additional Props `` renders a [MUI `` component](https://mui.com/components/autocomplete/) and it accepts the `` props: @@ -276,6 +450,63 @@ import { AutocompleteInput, ReferenceInput } from 'react-admin'; ``` +Whenever users type a string in the autocomplete input, `` calls `dataProvider.getList()` using the string as filter, to return a filtered list of possible options from the reference resource. This filter is built using the `filterToQuery` prop. You may want to customize that function to match the filtering capabilities of your API: + +```jsx +const filterToQuery = searchText => ({ name_ilike: `%${searchText}%` }); + + + + +``` + +Also, `` doesn't call `dataProvider.getList()` on every keystroke. It waits for the user to stop typing for 250ms before calling the API. You can customize this delay using the `debounce` prop: + +```jsx + + + +``` + +## Using A Custom Element For Options + +You can pass a custom element as [`optionText`](#optiontext) to have `` render each suggestion in a custom way. + +`` will render the custom option element inside a [``](./useRecordContext.md), using the related choice as the `record` prop. You can use Field components there. + +However, as the underlying MUI `` component requires that the current selection is a string, you must also pass a function as the `inputText` prop. This function should return a text representation of the current selection. You should also pass a `matchSuggestion` function to filter the choices based on the current selection. + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi', avatar:'/penguin' }, + { id: 456, first_name: 'Jane', last_name: 'Austen', avatar:'/panda' }, +]; +const OptionRenderer = () => { + const record = useRecordContext(); + return ( + + + {record.first_name} {record.last_name} + + ); +}; +const inputText = choice => `${choice.first_name} ${choice.last_name}`; +const matchSuggestion = (filter, choice) => { + return ( + choice.first_name.toLowerCase().includes(filter.toLowerCase()) + || choice.last_name.toLowerCase().includes(filter.toLowerCase()) + ); +}; + +} + inputText={inputText} + matchSuggestion={matchSuggestion} +/> +``` + ## 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/DualListInput.md b/docs/DualListInput.md index df6fa5c57cb..93df8347e25 100644 --- a/docs/DualListInput.md +++ b/docs/DualListInput.md @@ -5,17 +5,19 @@ title: "The DualListInput Component" # `` -This [Enterprise Edition](https://marmelab.com/ra-enterprise) component allows to edit array values, one-to-many or many-to-many relationships by moving items from one list to another. It's a good alternative to `` for a small number of choices. +This [Enterprise Edition](https://marmelab.com/ra-enterprise) component allows to edit array values, one-to-many or many-to-many relationships by moving items from one list to another. It's a good alternative to [``](./SelectArrayInput.md) for a small number of choices. ![DualListInput](https://marmelab.com/ra-enterprise/modules/assets/ra-relationships-duallistinput.gif) +## Usage + ```jsx -import { ReferenceInput } from 'react-admin'; -import { DualListInput } from '@react-admin/ra-relationships'; +import { ReferenceArrayInput } from "react-admin"; +import { DualListInput } from "@react-admin/ra-relationships"; - - - + + +; ``` Check [the `ra-relationships` documentation](https://marmelab.com/ra-enterprise/modules/ra-relationships) for more details. diff --git a/docs/Inputs.md b/docs/Inputs.md index a5889b60086..51f1f14289c 100644 --- a/docs/Inputs.md +++ b/docs/Inputs.md @@ -15,7 +15,7 @@ Input components are usually wrappers around MUI form components, bound to the c Input components must be used inside a Form element (e.g. [`
`](./Form.md), [``](./SimpleForm.md), [``](./TabbedForm.md)). These components create a [`react-hook-form`](https://react-hook-form.com/) form and context. -They require a `source` property. +Input components require a `source` prop. ```jsx import { Edit, SimpleForm, ReferenceInput, SelectInput, TextInput, required } from 'react-admin'; @@ -54,7 +54,35 @@ All input components accept the following props: React-admin uses [react-hook-form](https://react-hook-form.com/) to control form inputs. Each input component also accepts all react-hook-form [useController](https://react-hook-form.com/api/usecontroller) hook options. -Additional props are passed down to the underlying component (usually an MUI component). For instance, when setting the `variant` prop on a `TextInput` component, the underlying MUI `` receives it, and renders it with a different variant. Refer to the documentation of each Input component to see the underlying MUI component and its props. +Additional props are passed down to the underlying component (usually an MUI component). For instance, when setting the `variant` prop on a `` component, the underlying MUI `` receives it, and renders it with a different variant. Refer to the documentation of each Input component to see the underlying MUI component and its props. + +## Which Input Component to Use? + +React-admin provides a set of Input components, each one designed for a specific data type. Here is a list of the most common ones: + +| Data Type | Example value | Input Components | +|-----------------------|--------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| String | `'Lorem Ipsum'` | [``](./TextInput.md) | +| Rich text | `

Lorem Ipsum

` | [``](./RichTextInput.md) | +| Markdown | `# Lorem Ipsum` | [``](./MarkdownInput.md) | +| Password | `'********'` | [``](./PasswordInput.md) | +| Image URL | `'https://example.com/image.png'` | [``](./ImageInput.md) | +| File URL | `'https://example.com/file.pdf'` | [``](./FileInput.md) | +| Number | `42`, `1.345` | [``](./NumberInput.md) | +| Boolean | `true` | [``](./BooleanInput.md), [``](./NullableBooleanInput.md) | +| Date | `'2022-10-23'` | [``](./DateInput.md) | +| Time | `'14:30:00'` | [``](./TimeInput.md) | +| Date & time | `'2022-10-24T19:40:28.003Z'` | [``](./DateTimeInput.md) | +| Object | `{ foo: 'bar' }` | All inputs (see [ `source`](#source)) | +| Enum | `'foo'` | [``](./SelectInput.md), [``](./AutocompleteInput.md), [``](./RadioButtonGroupInput.md) | +| Foreign key | `42` | [``](./ReferenceInput.md) | +| Array of objects | `[{ item: 'jeans', qty: 3 }, { item: 'shirt', qty: 1 }]` | [``](./ArrayInput.md) | +| Array of Enums | `['foo', 'bar']` | [``](./SelectArrayInput.md), [``](./AutocompleteArrayInput.md), [``](./CheckboxGroupInput.md), [``](./DualListInput.md) | +| Array of foreign keys | `[42, 43]` | [``](./ReferenceArrayInput.md) | +| Translations | `{ en: 'Hello', fr: 'Bonjour' }` | [``](./TranslatableInputs.md) | +| Related records | `[{ id: 42, title: 'Hello' }, { id: 43, title: 'World' }]` | [``](./ReferenceManyInput.md), [``](./ReferenceManyToManyInput.md) | + + ## `className` diff --git a/docs/RadioButtonGroupInput.md b/docs/RadioButtonGroupInput.md index e2e48bde0c2..870978300b2 100644 --- a/docs/RadioButtonGroupInput.md +++ b/docs/RadioButtonGroupInput.md @@ -5,27 +5,52 @@ title: "The RadioButtonGroupInput Component" # `` -If you want to let the user choose a value among a list of possible values that are always shown (instead of hiding them behind a dropdown list, as in [``](./SelectInput.md)), `` is the right component. +If you want to let the user choose a value among a list of possible values that are always shown, `` is the right component. It renders using [MUI's ``](https://mui.com/material-ui/react-radio-button/). ![RadioButtonGroupInput](./img/radio-button-group-input.gif) -Set the `choices` attribute to determine the options (with `id`, `name` tuples): +This input allows editing record fields that are scalar values, e.g. `123`, `'admin'`, etc. + +## Usage + +In addition to the `source`, `` requires one prop: the `choices` listing the possible values. ```jsx import { RadioButtonGroupInput } from 'react-admin'; ``` -## Properties +By default, the possible choices are built from the `choices` prop, using: + - the `id` field as the option value, + - the `name` field as the option text + +The form value for the source must be the selected value, e.g. + +```js +{ + id: 123, + title: 'Lorem Ipsum', + category: 'lifestyle', +} +``` + +**Tip**: React-admin includes other components to edit such values: + + - [``](./SelectInput.md) renders a dropdown + - [``](./AutocompleteInput.md) renders a list of suggestions in an autocomplete input + +**Tip**: If you need to let users select more than one item in the list, check out the [``](./CheckboxGroupInput.md) component. + +## Props | Prop | Required | Type | Default | Description | | ----------------- | -------- | -------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| `choices` | Required | `Object[]` | - | List of items to show as options | +| `choices` | Optional | `Object[]` | - | List of items to show as options. Required unless inside a ReferenceInput. | | `options` | Optional | `Object` | - | Props to pass to the underlying `` element | | `optionText` | Optional | `string` | `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 | @@ -34,68 +59,96 @@ import { RadioButtonGroupInput } from 'react-admin'; `` also accepts the [common input props](./Inputs.md#common-input-props). -## Usage +## `choices` -You can customize the properties to use for the option name and value, thanks to the `optionText` and `optionValue` attributes: +An array of objects that represents the choices to show in the options. The objects must have at least two fields: one to use for the option name, and the other to use for the option value. By default, `` will use the `id` and `name` fields. ```jsx const choices = [ - { _id: 123, full_name: 'Leo Tolstoi', sex: 'M' }, - { _id: 456, full_name: 'Jane Austen', sex: 'F' }, + { id: 'tech', name: 'Tech' }, + { id: 'lifestyle', name: 'Lifestyle' }, + { id: 'people', name: 'People' }, ]; - + ``` -`optionText` also accepts a function, so you can shape the option text at will: +If the choices have different keys, you can use [`optionText`](#optiontext) and [`optionValue`](#optionvalue) to specify which fields to use for the name and value. ```jsx const choices = [ - { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, - { id: 456, first_name: 'Jane', last_name: 'Austen' }, + { _id: 'tech', label: 'Tech' }, + { _id: 'lifestyle', label: 'Lifestyle' }, + { _id: 'people', label: 'People' }, ]; -const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`; - + ``` -`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there. +The choices are translated by default, so you can use translation identifiers as choices: ```jsx const choices = [ - { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, - { id: 456, first_name: 'Jane', last_name: 'Austen' }, + { id: 'tech', name: 'myroot.categories.tech' }, + { id: 'lifestyle', name: 'myroot.categories.lifestyle' }, + { id: 'people', name: 'myroot.categories.people' }, ]; -const FullNameField = ({ record }) => {record.first_name} {record.last_name}; -}/> ``` -The choices are translated by default, so you can use translation identifiers as choices: +You can opt-out of this translation by setting [the `translateChoice` prop](#translatechoice) to `false`. + +If you need to *fetch* the options from another resource, you're actually editing a many-to-one or a one-to-one relationship. In this case, wrap the `` in a [``](./ReferenceInput.md). You don't need to specify the `choices` prop - the parent component injects it based on the possible values of the related resource. ```jsx -const choices = [ - { id: 'M', name: 'myroot.gender.male' }, - { id: 'F', name: 'myroot.gender.female' }, -]; + + + ``` -However, in some cases (e.g. inside a ``), you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`. +See [Using in a ``](#using-in-a-referenceinput) 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: ```jsx - +const possibleValues = ['tech', 'lifestyle', 'people']; +const ucfirst = name => name.charAt(0).toUpperCase() + name.slice(1); +const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) })); + + ``` -Lastly, use the `options` attribute if you want to override any of MUI's `` attributes: +## `options` + +Use the `options` attribute if you want to override any of MUI's `` attributes: {% raw %} ```jsx - + ``` {% endraw %} Refer to [MUI RadioGroup documentation](https://mui.com/api/radio-group) for more details. -**Tip**: If you want to populate the `choices` attribute with a list of related records, you should decorate `` with [``](./ReferenceInput.md), and leave the `choices` empty: +## `optionText` + +You can customize the property to use for the option name (instead of the default `name`) thanks to the `optionText` prop: + +```jsx +const choices = [ + { id: 'tech', label: 'Tech' }, + { id: 'lifestyle', label: 'Lifestyle' }, + { id: 'people', label: 'People' }, +]; + +``` + +`optionText` is particularly useful when the choices are records fetched from another resource, and `` is a child of a [``](./ReferenceInput.md). 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. ```jsx import { RadioButtonGroupInput, ReferenceInput } from 'react-admin'; @@ -105,6 +158,66 @@ import { RadioButtonGroupInput, ReferenceInput } from 'react-admin'; ``` +See [Using in a ``](#using-in-a-referenceinput) below for more details. + +`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' }, +]; +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. + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, + { id: 456, first_name: 'Jane', last_name: 'Austen' }, +]; + +const FullNameField = () => { + const record = useRecordContext(); + return {record.first_name} {record.last_name}; +} + +}/> +``` + +## `optionValue` + +You can customize the property to use for the option value (instead of the default `id`) thanks to the `optionValue` prop: + +```jsx +const choices = [ + { _id: 'tech', name: 'Tech' }, + { _id: 'lifestyle', name: 'Lifestyle' }, + { _id: 'people', name: 'People' }, +]; + +``` + +## `row` + +By default, the radio buttons are displayed in a row. You can change that and let react-admin render one choice per row by setting the `row` prop to `false`: + +```jsx + +``` + +![RadioButtonGroupInput row false](./img/radio-button-group-input-row.gif) + ## `sx`: CSS API The `` component accepts the usual `className` prop. You can also override many styles of the inner components thanks to the `sx` property (as most MUI components, see their [documentation about it](https://mui.com/customization/how-to-customize/#overriding-nested-component-styles)). This property accepts the following subclasses: @@ -114,3 +227,44 @@ The `` component accepts the usual `className` prop. You | `& .RaRadioButtonGroupInput-label` | Applied to the underlying MUI's `FormLabel` component | To override the style of all instances of `` using the [MUI style overrides](https://mui.com/customization/globals/#css), use the `RaRadioButtonGroupInput` key. + +## `translateChoice` + +The choices are translated by default, so you can use translation identifiers as choices: + +```jsx +const choices = [ + { id: 'M', name: 'myroot.gender.male' }, + { id: 'F', name: 'myroot.gender.female' }, +]; +``` + +However, in some cases, you may not want the choice to be translated. In that case, set the `translateChoice` prop to `false`. + +```jsx + +``` + +Note that `translateChoice` is set to `false` when `` is a child of ``. + +## Using In A ReferenceInput + +If you want to populate the `choices` attribute with a list of related records, you should decorate `` with [``](./ReferenceInput.md), and leave the `choices` empty: + +```jsx +import { RadioButtonGroupInput, ReferenceInput } from 'react-admin'; + + + + +``` + +In that case, `` 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: + +```jsx +import { RadioButtonGroupInput, ReferenceInput } from 'react-admin'; + + + + +``` \ No newline at end of file diff --git a/docs/Reference.md b/docs/Reference.md index 5be744385d7..4061d83901f 100644 --- a/docs/Reference.md +++ b/docs/Reference.md @@ -177,7 +177,7 @@ title: "Reference" * [`useGetPermissions`](./Authentication.md#usegetpermissions-hook) * [`useHasLock`](https://marmelab.com/ra-enterprise/modules/ra-realtime#locks-on-content) * [`useHasLocks`](https://marmelab.com/ra-enterprise/modules/ra-realtime#locks-on-content) -* [`useInput`](./Inputs.md#the-useinput-hook) +* [`useInput`](./useInput.md) * [`useList`](./useList.md) * [`useListContext`](./useListContext.md) * [`useListController`](./useListController.md) diff --git a/docs/ReferenceArrayInput.md b/docs/ReferenceArrayInput.md index c54bdaee280..1f3ef9cd128 100644 --- a/docs/ReferenceArrayInput.md +++ b/docs/ReferenceArrayInput.md @@ -27,7 +27,7 @@ http://myapi.com/tags?page=1&perPage=25 Once it receives the deduplicated reference resources, this component delegates rendering to its child component, by providing the possible choices through the `ChoicesContext`. This context value can be accessed with the [`useChoicesContext`](./useChoicesContext.md) hook. -This means you can use `` with [``](./SelectArrayInput.md), or with the component of your choice, provided they detect a `ChoicesContext` is available and get their choices from it. +This means you can use `` with [``](./SelectArrayInput.md), [``](./AutocompleteArrayInput.md), [``](./DualListInput.md), [``](./CheckboxGroupInput.md), or with the component of your choice, provided they detect a `ChoicesContext` is available and get their choices from it. The component expects a `source` and a `reference` attributes. For instance, to make the `tag_ids` for a `post` editable: diff --git a/docs/ReferenceInput.md b/docs/ReferenceInput.md index 8d40c7ee5be..841b7cbcf2a 100644 --- a/docs/ReferenceInput.md +++ b/docs/ReferenceInput.md @@ -5,40 +5,105 @@ title: "The ReferenceInput Component" # `` -Use `` for foreign-key values, for instance, to edit the `post_id` of a `comment` resource. +Use `` for foreign-key values, for instance, to edit the `company_id` of a `contact` resource. ![ReferenceInput](./img/reference-input.gif) ## Usage -The component expects a `source` and a `reference` attributes. For instance, to make the `post_id` for a `comment` editable: +For instance, a contact record has a `company_id` field, which is a foreign key to a company record. + +``` +┌──────────────┐ ┌────────────┐ +│ contacts │ │ companies │ +│--------------│ │------------│ +│ id │ ┌───│ id │ +│ first_name │ │ │ name │ +│ last_name │ │ │ address │ +│ company_id │───┘ └────────────┘ +└──────────────┘ +``` + +To make the `company_id` for a `contact` editable, use the following syntax: ```jsx -import { ReferenceInput } from 'react-admin'; +import { Edit, SimpleForm, TextInput, ReferenceInput } from 'react-admin'; + +const ContactEdit = () => ( + + + + + + + +); +``` + +`` require a `source` and a `reference` prop. + +`` uses the foreign key value to fetch the related record. It also grabs the list of possible choices for the field. For instance, if the `ContactEdit` component above is used to edit the following contact: - +```js +{ + id: 123, + first_name: 'John', + last_name: 'Doe', + company_id: 456 +} ``` -This component fetches the related record (using `dataProvider.getMany()`) as well as possible choices (using `dataProvider.getList()` in the reference resource). +Then `` will issue the following queries: + +```js +dataProvider.getMany('companies', { ids: [456] }); +dataProvider.getList('companies', { + filter: {}, + sort: { field: 'id', order: 'DESC' }, + pagination: { page: 1, perPage: 25 } +}); +``` + +`` renders an [``](./AutocompleteInput.md) to let the user select the related record. Users can narrow down the choices by typing a search term in the input. This modifies the query sent to the `dataProvider` as follows: + +```js +dataProvider.getList('companies', { + filter: { q: [search term] }, + sort: { field: 'id', order: 'DESC' }, + pagination: { page: 1, perPage: 25 } +}); +``` -`` renders an [``](./AutocompleteInput.md) to let the user select the related record. +See [Customizing the filter query](#customizing-the-filter-query) below for more information about how to change `filter` prop based on the `` search term. -You can tweak how this component fetches the possible values using the `page`, `perPage`, `sort`, and `filter` props. +You can tweak how `` fetches the possible values using the `page`, `perPage`, `sort`, and `filter` props. + +You can replace the default `` by another choice input. To do so, pass the choice input component as `` child. For instance, to use a ``: + +```jsx +import { ReferenceInput, SelectInput } from 'react-admin'; + + + + +``` + +See the [`children`](#children) section for more details. ## Props | Prop | Required | Type | Default | Description | |--------------------|----------|---------------------------------------------|----------------------------------|------------------------------------------------------------------------------------------------| | `source` | Required | `string` | - | Name of the entity property to use for the input value | -| `label` | Optional | `string` | - | Useful only when `ReferenceInput` is in a Filter array, the label is used as the Filter label. | -| `reference` | Required | `string` | '' | Name of the reference resource, e.g. 'posts'. | -| `children` | Optional | `ReactNode` | `` | The actual selection component | +| `reference` | Required | `string` | '' | Name of the reference resource, e.g. 'companies'. | +| `children` | Optional | `ReactNode` | `` | The actual selection component | +| `enableGet Choices` | Optional | `({q: string}) => boolean` | `() => true` | Function taking the `filterValues` and returning a boolean to enable the `getList` call. | | `filter` | Optional | `Object` | `{}` | Permanent filters to use for getting the suggestion list | +| `label` | Optional | `string` | - | Useful only when `ReferenceInput` is in a Filter array, the label is used as the Filter label. | | `page` | Optional | `number` | 1 | The current page number | | `perPage` | Optional | `number` | 25 | Number of suggestions to show | -| `sort` | Optional | `{ field: String, order: 'ASC' or 'DESC' }` | `{ field: 'id', order: 'DESC' }` | How to order the list of suggestions | -| `enableGetChoices` | Optional | `({q: string}) => boolean` | `() => true` | Function taking the `filterValues` and returning a boolean to enable the `getList` call. | | `queryOptions` | Optional | [`UseQueryOptions`](https://tanstack.com/query/v4/docs/reference/useQuery?from=reactQueryV3&original=https://react-query-v3.tanstack.com/reference/useQuery) | `{}` | `react-query` client options | +| `sort` | Optional | `{ field: String, order: 'ASC' or 'DESC' }` | `{ field:'id', order:'DESC' }` | How to order the list of suggestions | **Note**: `` doesn't accept the [common input props](./Inputs.md#common-input-props) (like `label`) ; it is the responsibility of the child component to apply them. @@ -53,8 +118,8 @@ For instance, to customize the input label, set the `label` prop on the child co ```jsx import { ReferenceInput, AutocompleteInput } from 'react-admin'; - - + + ``` @@ -63,7 +128,7 @@ You can also use [``](./SelectInput.md) or [` + ``` @@ -78,8 +143,8 @@ You can make the `getList()` call lazy by using the `enableGetChoices` prop. Thi ```jsx q.length >= 2} /> ``` @@ -90,10 +155,12 @@ You can filter the query used to populate the possible values. Use the `filter` {% raw %} ```jsx - + ``` {% endraw %} +**Note**: When users type a search term in the ``, this doesn't affect the `filter` prop. Check the [Customizing the filter query](#customizing-the-filter-query) section below for details on how that filter works. + ## `format` By default, children of `` transform `null` values from the `dataProvider` into empty strings. @@ -105,7 +172,7 @@ For instance, if you want to transform an option value before rendering, and the ```jsx import { ReferenceInput, AutocompleteInput } from 'react-admin'; - + value == null ? 'not defined' : value} /> ``` @@ -115,7 +182,7 @@ The same goes if the child is a ``: ```jsx import { ReferenceInput, SelectInput } from 'react-admin'; - + value === undefined ? 'not defined' : null} /> ``` @@ -127,8 +194,8 @@ In an `` or `` view, the `label` prop has no effect. ` - + + ``` @@ -136,8 +203,8 @@ In a Filter form, react-admin uses the `label` prop to set the Filter label. So ```jsx const filters = [ - - + + , ]; ``` @@ -153,7 +220,7 @@ For instance, if you want to transform an option value before submission, and th ```jsx import { ReferenceInput, AutocompleteInput } from 'react-admin'; - + value === 'not defined' ? null : value} /> ``` @@ -163,7 +230,7 @@ The same goes if the child is a ``: ```jsx import { ReferenceInput, SelectInput } from 'react-admin'; - + value === 'not defined' ? undefined : null} /> ``` @@ -173,29 +240,63 @@ import { ReferenceInput, SelectInput } from 'react-admin'; By default, `` fetches only the first 25 values. You can extend this limit by setting the `perPage` prop. ```jsx - + ``` +This prop is mostly useful when using [``](./SelectInput.md) or [``](./RadioButtonGroupInput.md) as child, as the default `` child allows to filter the possible choices with a search input. + ## `reference` -The name of the reference resource. For instance, in a Post form, if you want to edit the post author, the reference should be "authors". +The name of the reference resource. For instance, in a contact form, if you want to edit the contact employer, the reference should be "companies". ```jsx - + ``` -`` will use the reference resource `recordRepresentation` to display the selected record and the list of possible records. +`` will use the reference resource [`recordRepresentation`](./Resource.md#recordrepresentation) to display the selected record and the list of possible records. So for instance, if the `companies` resource is defined as follow: + +```jsx + +``` + +Then `` will display the company name in the input and in the list of possible values. + +You can override this default by specifying the `optionText` prop in the child component. For instance, for an ``: + +```jsx + + + +``` + +## `queryOptions` + +Use the `queryOptions` prop to pass options to the `dataProvider.getList()` query that fetches the possible choices. + +For instance, to pass [a custom `meta`](./Actions.md#meta-parameter): + +{% raw %} +```jsx + +``` +{% endraw %} ## `sort` -By default, `` orders the possible values by `id` desc. You can change this order by setting the `sort` prop (an object with `field` and `order` properties). +By default, `` orders the possible values by `id` desc. + +You can change this order by setting the `sort` prop (an object with `field` and `order` properties). {% raw %} ```jsx ``` {% endraw %} @@ -204,44 +305,41 @@ By default, `` orders the possible values by `id` desc. You can The name of the property in the record that contains the identifier of the selected record. -For instance, if a Post contains a reference to an author via an `author_id` property: +For instance, if a contact contains a reference to a company via a `company_id` property: -```json +```js { - "id": 456, - "title": "Hello world", - "author_id": 12 + id: 456, + firstName: "John", + lastName: "Doe", + company_id: 12, } ``` -Then to display a selector for the post author, you should call `` as follows: +Then to display a selector for the contact company, you should call `` as follows: ```jsx - + ``` -## queryOptions +## Customizing The Filter Query -Use [the `queryOptions` prop](#queryoptions) to pass [a custom `meta`](./Actions.md#meta-parameter) to the `dataProvider.getList()` call. +By default, `` renders an ``, which lets users type a search term to filter the possible values. `` calls `dataProvider.getList()` using the search term as filter, using the format `filter: { q: [search term] }`. + +If you want to customize the conversion between the search term and the query filter to match the filtering capabilities of your API, use the [``](./AutocompleteInput.md#filtertoquery) prop. -{% raw %} ```jsx -import { ReferenceInput, AutocompleteInput } from 'react-admin'; +const filterToQuery = searchText => ({ name_ilike: `%${searchText}%` }); - - + + ``` -{% endraw %} ## Performance Why does `` use the `dataProvider.getMany()` method with a single value `[id]` instead of `dataProvider.getOne()` to fetch the record for the current value? -Because when there may be many `` for the same resource in a form (for instance when inside an ``), so react-admin *aggregates* the calls to `dataProvider.getMany()` into a single one with `[id1, id2, ...]`. +Because when there may be many `` for the same resource in a form (for instance when inside an ``), react-admin *aggregates* the calls to `dataProvider.getMany()` into a single one with `[id1, id2, ...]`. This speeds up the UI and avoids hitting the API too much. diff --git a/docs/SelectInput.md b/docs/SelectInput.md index 39b17a8f780..e766fcdeee1 100644 --- a/docs/SelectInput.md +++ b/docs/SelectInput.md @@ -9,27 +9,48 @@ To let users choose a value in a list using a dropdown, use ``. It ![SelectInput](./img/select-input.gif) +This input allows editing record fields that are scalar values, e.g. `123`, `'admin'`, etc. + ## Usage -Set the `choices` attribute to determine the options (with `id`, `name` tuples): +In addition to the `source`, `` requires one prop: the `choices` listing the possible values. ```jsx import { SelectInput } from 'react-admin'; ``` -If, instead of showing choices as a dropdown list, you prefer to display them as a list of radio buttons, try the [``](./RadioButtonGroupInput.md). And if the list is too big, prefer the [``](./AutocompleteInput.md). +By default, the possible choices are built from the `choices` prop, using: + - the `id` field as the option value, + - the `name` field as the option text + +The form value for the source must be the selected value, e.g. + +```js +{ + id: 123, + title: 'Lorem Ipsum', + category: 'lifestyle', +} +``` + +**Tip**: React-admin includes other components to edit such values: + + - [``](./RadioButtonGroupInput.md) renders a list of radio buttons + - [``](./AutocompleteInput.md) renders a list of suggestions in an autocomplete input + +**Tip**: If you need to let users select more than one item in the list, check out the [``](./SelectArrayInput.md) component. ## Props | Prop | Required | Type | Default | Description | |-------------------|----------|----------------------------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------| -| `choices` | Optional | `Object[]` | - | List of items to show as options. Required if not inside a ReferenceInput. | +| `choices` | Optional | `Object[]` | - | List of items to show as options. Required unless inside a ReferenceInput. | | `create` | Optional | `Element` | `-` | A React Element to render when users want to create a new choice | | `createLabel` | Optional | `string` | `ra.action.create` | The label for the menu item allowing users to create a new choice. Used when the filter is empty | | `disableValue` | Optional | `string` | 'disabled' | The custom field name used in `choices` to disable some choices | @@ -50,11 +71,10 @@ An array of objects that represents the choices to show in the dropdown. The obj ```jsx const choices = [ - { id: 'programming', name: 'Programming' }, + { id: 'tech', name: 'Tech' }, { id: 'lifestyle', name: 'Lifestyle' }, - { id: 'photography', name: 'Photography' }, + { id: 'people', name: 'People' }, ]; - ``` @@ -62,19 +82,29 @@ If the choices have different keys, you can use [`optionText`](#optiontext) and ```jsx const choices = [ - { name: 'programming', label: 'Programming' }, - { name: 'lifestyle', label: 'Lifestyle' }, - { name: 'photography', label: 'Photography' }, + { _id: 'tech', label: 'Tech' }, + { _id: 'lifestyle', label: 'Lifestyle' }, + { _id: 'people', label: 'People' }, ]; - ``` +You can render some options as disabled by setting the `disabled` field in some choices: + +```jsx +const choices = [ + { id: 'tech', name: 'Tech' }, + { id: 'lifestyle', name: 'Lifestyle' }, + { id: 'people', name: 'People', disable: true }, +]; + +``` + `` adds an empty option by default, to let users enter an empty value. You can disable this behavior by marking the input as required using the `validate` prop: ```jsx @@ -83,7 +113,19 @@ import { SelectInput, required } from 'react-admin'; ``` -When used inside a ``, `` doesn't need a `choices` prop. Instead, it will use the records fetched by `` as choices, via the `ChoicesContext`. +The choices are translated by default, so you can use translation identifiers as choices: + +```jsx +const choices = [ + { id: 'tech', name: 'myroot.categories.tech' }, + { id: 'lifestyle', name: 'myroot.categories.lifestyle' }, + { id: 'people', name: 'myroot.categories.people' }, +]; +``` + +You can opt-out of this translation by setting [the `translateChoice` prop](#translatechoice) to `false`. + +If you need to *fetch* the options from another resource, you're actually editing a many-to-one or a one-to-one relationship. In this case, wrap the `` in a [``](./ReferenceInput.md). You don't need to specify the `choices` prop - the parent component injects it based on the possible values of the related resource. ```jsx @@ -93,17 +135,112 @@ When used inside a ``, `` doesn't need a `choices` See [Using in a `ReferenceInput>`](#using-in-a-referenceinput) 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: + +```jsx +const possibleValues = ['tech', 'lifestyle', 'people']; +const ucfirst = name => name.charAt(0).toUpperCase() + name.slice(1); +const choices = possibleValues.map(value => ({ id: value, name: ucfirst(value) })); + + +``` + +## `create` + +To allow users to add new options, pass a React element as the `create` prop. `` will then render a menu item at the bottom of the list, which will render the passed element when clicked. + +{% raw %} +```jsx +import { CreateCategory } from './CreateCategory'; + +const PostCreate = () => ( + + + + + } /> + + + +); + +// in ./CreateCategory.js +import { useCreate, useCreateSuggestionContext } from 'react-admin'; +import { + Box, + BoxProps, + Button, + Dialog, + DialogActions, + DialogContent, + TextField, +} from '@mui/material'; + +const CreateCategory = () => { + const { filter, onCancel, onCreate } = useCreateSuggestionContext(); + const [create] = useCreate(); + const [value, setValue] = React.useState(filter || ''); + + const handleSubmit = event => { + event.preventDefault(); + create( + 'categories', + { data: { title: value } }, + { + onSuccess: (data) => { + setValue(''); + onCreate(data); + }, + } + ); + }; + + return ( + + + + setValue(event.target.value)} + autoFocus + /> + + + + + + + + ); +}; +``` +{% endraw %} + +If you just need to ask users for a single string to create the new option, you can use [the `onCreate` prop](#oncreate) instead. + ## `disableValue` -You can use a custom field name by setting `disableValue` prop: +By default, `` renders the choices with the field `disabled: true` as disabled. + +```jsx +const choices = [ + { id: 'tech', name: 'Tech' }, + { id: 'lifestyle', name: 'Lifestyle' }, + { id: 'people', name: 'People', disabled: true }, +]; + +``` + +If you want to use another field to denote disabled options, set the `disableValue` prop. ```jsx const choices = [ - { _id: 123, full_name: 'Leo Tolstoi', sex: 'M' }, - { _id: 456, full_name: 'Jane Austen', sex: 'F' }, - { _id: 987, full_name: 'Jack Harden', sex: 'M', not_available: true }, + { id: 'tech', name: 'Tech' }, + { id: 'lifestyle', name: 'Lifestyle' }, + { id: 'people', name: 'People', not_available: true }, ]; - + ``` ## `emptyText` @@ -113,15 +250,7 @@ If the input isn't required (using `validate={required()}`), users can select an You can override that label with the `emptyText` prop. ```jsx - + ``` The `emptyText` prop accepts either a string or a React Element. @@ -129,15 +258,7 @@ The `emptyText` prop accepts either a string or a React Element. And if you want to hide that empty choice, make the input required. ```jsx - + ``` ## `emptyValue` @@ -147,27 +268,54 @@ If the input isn't required (using `validate={required()}`), users can select an You can override this value with the `emptyValue` prop. ```jsx - + ``` **Tip**: While you can set `emptyValue` to a non-string value (e.g. `0`), you cannot use `null` or `undefined`, as it would turn the `` into an [uncontrolled component](https://reactjs.org/docs/uncontrolled-components.html). If you need the empty choice to be stored as `null` or `undefined`, use [the `parse` prop](./Inputs.md#parse) to convert the default empty value ('') to `null` or `undefined`, or use [the `sanitizeEmptyValues` prop](./SimpleForm.md#sanitizeemptyvalues) on the Form component. +## `onCreate` + +Use the `onCreate` prop to allow users to create new options on-the-fly. Its value must be a function. This lets you render a `prompt` to ask users about the new value. You can return either the new choice directly or a Promise resolving to the new choice. + +{% raw %} +```js +import { SelectInput, Create, SimpleForm, TextInput } from 'react-admin'; + +const PostCreate = () => { + const categories = [ + { name: 'Tech', id: 'tech' }, + { name: 'Lifestyle', id: 'lifestyle' }, + ]; + return ( + + + + { + const newCategoryName = prompt('Enter a new category'); + const newCategory = { id: newCategoryName.toLowerCase(), name: newCategoryName }; + categories.push(newCategory); + return newCategory; + }} + source="category" + choices={categories} + /> + + + ); +} +``` +{% endraw %} + +If a prompt is not enough, you can use [the `create` prop](#create) to render a custom component instead. + ## `options` Use the `options` attribute if you want to override any of MUI's `` attributes: {% raw %} ```jsx - + ``` {% endraw %} @@ -175,17 +323,29 @@ Refer to [MUI Select documentation](https://mui.com/api/select) for more details ## `optionText` -You can customize the choice field to use for the option name, thanks to the `optionText` attribute: +You can customize the property to use for the option name (instead of the default `name`) thanks to the `optionText` prop: ```jsx -// no 'name' field in the choices const choices = [ - { id: 123, full_name: 'Leo Tolstoi', sex: 'M' }, - { id: 456, full_name: 'Jane Austen', sex: 'F' }, + { id: 'tech', label: 'Tech' }, + { id: 'lifestyle', label: 'Lifestyle' }, + { id: 'people', label: 'People' }, ]; - + ``` +`optionText` is particularly useful when the choices are records fetched from another resource, and `` is a child of a [``](./ReferenceInput.md). 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. + +```jsx +import { SelectInput, ReferenceInput } from 'react-admin'; + + + + +``` + +See [Using in a ``](#using-in-a-referenceinput) below for more details. + `optionText` also accepts a function, so you can shape the option text at will: ```jsx @@ -197,47 +357,45 @@ const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`; ``` -`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there. +`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. ```jsx const choices = [ { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, { id: 456, first_name: 'Jane', last_name: 'Austen' }, ]; -const FullNameField = ({ record }) => {record.first_name} {record.last_name}; -}/> + +const FullNameField = () => { + const record = useRecordContext(); + return {record.first_name} {record.last_name}; +} + +}/> ``` ## `optionValue` -You can customize the choice field to use for the option value, thanks to the `optionValue` attribute: +You can customize the property to use for the option value (instead of the default `id`) thanks to the `optionValue` prop: ```jsx -// no 'id' field in the choices const choices = [ - { _id: 123, name: 'Leo Tolstoi' }, - { _id: 456, name: 'Jane Austen' }, + { _id: 'tech', name: 'Tech' }, + { _id: 'lifestyle', name: 'Lifestyle' }, + { _id: 'people', name: 'People' }, ]; - + ``` ## `resettable` You can make the `SelectInput` component resettable using the `resettable` prop. This will add a reset button which will be displayed only when the field has a value. -![resettable SelectInput](./img/resettable-select-input.png) - -You can set disabled values by setting the `disabled` property of one item: - ```jsx -const choices = [ - { _id: 123, full_name: 'Leo Tolstoi', sex: 'M' }, - { _id: 456, full_name: 'Jane Austen', sex: 'F' }, - { _id: 1, full_name: 'System Administrator', sex: 'F', disabled: true }, -]; - + ``` +![resettable SelectInput](./img/resettable-select-input.png) + ## `sx`: CSS API The `` component accepts the usual `className` prop. You can also override many styles of the inner components thanks to the `sx` property (as most MUI components, see their [documentation about it](https://mui.com/customization/how-to-customize/#overriding-nested-component-styles)). This property accepts the following subclasses: diff --git a/docs/img/autocomplete-input.gif b/docs/img/autocomplete-input.gif index 3e4675f28a9..d193a1b6866 100644 Binary files a/docs/img/autocomplete-input.gif and b/docs/img/autocomplete-input.gif differ diff --git a/docs/img/radio-button-group-input-row.gif b/docs/img/radio-button-group-input-row.gif new file mode 100644 index 00000000000..3a00fa7df9f Binary files /dev/null and b/docs/img/radio-button-group-input-row.gif differ diff --git a/docs/img/radio-button-group-input.gif b/docs/img/radio-button-group-input.gif index 731685972bf..6235e064e7e 100644 Binary files a/docs/img/radio-button-group-input.gif and b/docs/img/radio-button-group-input.gif differ diff --git a/docs/img/reference-input.gif b/docs/img/reference-input.gif index 271e3b356bc..d4843e4b7bb 100644 Binary files a/docs/img/reference-input.gif and b/docs/img/reference-input.gif differ diff --git a/docs/navigation.html b/docs/navigation.html index 42448d3a34a..7614c06c22b 100644 --- a/docs/navigation.html +++ b/docs/navigation.html @@ -186,6 +186,7 @@
  • <ReferenceManyInput>
  • <ReferenceManyToManyInput>
  • <SimpleFormIterator>
  • +
  • useInput
    • The Store
      diff --git a/docs/useInput.md b/docs/useInput.md new file mode 100644 index 00000000000..accb40c188c --- /dev/null +++ b/docs/useInput.md @@ -0,0 +1,148 @@ +--- +layout: default +title: "useInput" +--- + +# `useInput` + +This hook lets you build custom inputs for react-admin. It's a wrapper around [react-hook-form's `useController`](https://react-hook-form.com/api/usecontroller). + +React-admin adds functionality to react-hook-form: + +- handling of custom event emitters like `onChange`, +- support for an array of validators, +- detection of required fields to add an asterisk to the field label, +- parse and format to translate record values to form values and vice-versa. + +## Usage + +`useInput` expects at least a `source`, and returns an object with the following properties: + +```jsx +{ id, field, fieldState, formState, isRequired } +``` + +For instance, to build a custom input for a `title` field: + +```jsx +import { useInput } from 'react-admin'; + +const TitleInput = ({ source, label }) => { + const { id, field, fieldState } = useInput({ source }); + return ( + + ); +}; +``` + +## Props + +| Prop | Required | Type | Default | Description | +|----------------|----------|--------------------------------|---------|-------------------------------------------------------------------| +| `source` | Required | `string` | - | The name of the field in the record | +| `defaultValue` | Optional | `any` | - | The default value of the input | +| `format` | Optional | `Function` | - | A function to format the value from the record to the input value | +| `parse` | Optional | `Function` | - | A function to parse the value from the input to the record value | +| `validate` | Optional | `Function` | `Function[]` | - | A function or an array of functions to validate the input value | +| `id` | Optional | `string` | - | The id of the input | +| `onChange` | Optional | `Function` | - | A function to call when the input value changes | +| `onBlur` | Optional | `Function` | - | A function to call when the input is blurred | + +Additional props are passed to [react-hook-form's `useController` hook](https://react-hook-form.com/api/usecontroller). + +## Usage with MUI `` + +```jsx +// in LatLongInput.js +import TextField from '@mui/material/TextField'; +import { useInput, required } from 'react-admin'; + +const BoundedTextField = (props) => { + const { onChange, onBlur, ...rest } = props; + const { + field, + fieldState: { isTouched, invalid, error }, + formState: { isSubmitted }, + isRequired + } = useInput({ + // Pass the event handlers to the hook but not the component as the field property already has them. + // useInput will call the provided onChange and onBlur in addition to the default needed by react-hook-form. + onChange, + onBlur, + ...props, + }); + + return ( + + ); +}; +const LatLngInput = props => { + const { source, ...rest } = props; + + return ( + + +   + + + ); +}; +``` + +## Usage with MUI ` + Male + Female + + ); +}; +export default SexInput; +``` + +**Tip**: `useInput` accepts all arguments that you can pass to `useController`. Besides, components using `useInput` accept props like `format` and `parse`, to convert values from the form to the input, and vice-versa: + +```jsx +const parse = value => {/* ... */}; +const format = value => {/* ... */}; + +const PersonEdit = () => ( + + + formValue === 0 ? 'M' : 'F'} + parse={inputValue => inputValue === 'M' ? 0 : 1} + /> + + +); +``` diff --git a/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx b/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx index 865f1201e1e..94e8f34e685 100644 --- a/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/AutocompleteInput.stories.tsx @@ -231,10 +231,10 @@ const BookEditCustomOptions = () => { const searchTextLower = searchText.toLowerCase(); const match = record.fullName - .toLowerCase() + ?.toLowerCase() .includes(searchTextLower) || record.language - .toLowerCase() + ?.toLowerCase() .includes(searchTextLower); return match; diff --git a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx index 4d7b008af2c..4bcca68cdfc 100644 --- a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.stories.tsx @@ -10,14 +10,43 @@ import { FormInspector } from './common.stories'; export default { title: 'ra-ui-materialui/input/RadioButtonGroupInput' }; -const choices = [ - { id: 'M', name: 'Male' }, - { id: 'F', name: 'Female' }, -]; - export const Basic = () => ( - + + +); + +export const Row = () => ( + + + +); + +export const DefaultValue = () => ( + + ); diff --git a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.tsx b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.tsx index daa6a4f126a..6ebdf8e0a96 100644 --- a/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.tsx +++ b/packages/ra-ui-materialui/src/input/RadioButtonGroupInput.tsx @@ -95,6 +95,7 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => { margin = 'dense', onBlur, onChange, + options, optionText, optionValue, parse, @@ -182,6 +183,7 @@ export const RadioButtonGroupInput = (props: RadioButtonGroupInputProps) => { id={id} row={row} {...field} + {...options} {...sanitizeRestProps(rest)} > {allChoices.map(choice => ( @@ -271,6 +273,7 @@ export type RadioButtonGroupInputProps = Omit & ChoicesProps & FormControlProps & RadioGroupProps & { + options?: RadioGroupProps; source?: string; };