diff --git a/docs/ArrayInput.md b/docs/ArrayInput.md index 03ddb9da29..28a3da2c62 100644 --- a/docs/ArrayInput.md +++ b/docs/ArrayInput.md @@ -133,3 +133,54 @@ const OrderEdit = () => ( ); ``` + +## Changing An Item's Value Programmatically + +You can leverage `react-hook-form`'s [`setValue`](https://react-hook-form.com/docs/useform/setvalue) method to change an item's value programmatically. + +However you need to know the `name` under which the input was registered in the form, and this name is dynamically generated depending on the index of the item in the array. + +To get the name of the input for a given index, you can leverage the `SourceContext` created by react-admin, which can be accessed using the `useSourceContext` hook. + +This context provides a `getSource` function that returns the effective `source` for an input in the current context, which you can use as input name for `setValue`. + +Here is an example where we leverage `getSource` and `setValue` to change the role of an user to 'admin' when the 'Make Admin' button is clicked: + +{% raw %} + +```tsx +import { ArrayInput, SimpleFormIterator, TextInput, useSourceContext } from 'react-admin'; +import { useFormContext } from 'react-hook-form'; +import { Button } from '@mui/material'; + +const MakeAdminButton = () => { + const sourceContext = useSourceContext(); + const { setValue } = useFormContext(); + + const onClick = () => { + // sourceContext.getSource('role') will for instance return + // 'users.0.role' + setValue(sourceContext.getSource('role'), 'admin'); + }; + + return ( + + ); +}; + +const UserArray = () => ( + + + + + + + +); +``` + +{% endraw %} + +**Tip:** If you only need the item's index, you can leverage the [`useSimpleFormIteratorItem` hook](./SimpleFormIterator.md#getting-the-element-index) instead. \ No newline at end of file diff --git a/docs/Inputs.md b/docs/Inputs.md index 30186752cd..7f1a34f72c 100644 --- a/docs/Inputs.md +++ b/docs/Inputs.md @@ -647,7 +647,7 @@ const OrderEdit = () => ( ); ``` -**Tip**: When used inside an `ArrayInput`, `` provides one additional property to its child function called `scopedFormData`. It's an object containing the current values of the *currently rendered item*. This allows you to create dependencies between inputs inside a ``, as in the following example: +**Tip**: When used inside an ``, `` provides one additional property to its child function called `scopedFormData`. It's an object containing the current values of the *currently rendered item*. This allows you to create dependencies between inputs inside a ``, as in the following example: ```tsx import { FormDataConsumer } from 'react-admin'; @@ -682,6 +682,8 @@ const PostEdit = () => ( **Tip:** TypeScript users will notice that `scopedFormData` is typed as an optional parameter. This is because the `` component can be used outside of an `` and in that case, this parameter will be `undefined`. If you are inside an ``, you can safely assume that this parameter will be defined. +**Tip:** If you need to access the *effective* source of an input inside an ``, for example to change the value programmatically using `setValue`, you will need to leverage the [`useSourceContext` hook](./ArrayInput#changing-an-items-value-programmatically). + ## Hiding Inputs Based On Other Inputs You may want to display or hide inputs based on the value of another input - for instance, show an `email` input only if the `hasEmail` boolean input has been ticked to `true`. diff --git a/docs/ReferenceManyInput.md b/docs/ReferenceManyInput.md index 615e5f1569..68c1224255 100644 --- a/docs/ReferenceManyInput.md +++ b/docs/ReferenceManyInput.md @@ -300,3 +300,63 @@ const ProductEdit = () => ( - `` cannot be used with `undoable` mutations in a `` view. - `` cannot have a `` or a `` as one of its children. - `` does not support server side validation. + +## Changing An Item's Value Programmatically + +You can leverage `react-hook-form`'s [`setValue`](https://react-hook-form.com/docs/useform/setvalue) method to change an item's value programmatically. + +However you need to know the `name` under which the input was registered in the form, and this name is dynamically generated depending on the index of the item in the array. + +To get the name of the input for a given index, you can leverage the `SourceContext` created by react-admin, which can be accessed using the `useSourceContext` hook. + +This context provides a `getSource` function that returns the effective `source` for an input in the current context, which you can use as input name for `setValue`. + +Here is an example where we leverage `getSource` and `setValue` to prefill the email input when the 'Prefill email' button is clicked: + +{% raw %} + +```tsx +import { SimpleFormIterator, TextInput, useSourceContext } from 'react-admin'; +import { ReferenceManyInput } from '@react-admin/ra-relationships'; +import { useFormContext } from 'react-hook-form'; +import { Button } from '@mui/material'; + +const PrefillEmail = () => { + const sourceContext = useSourceContext(); + const { setValue, getValues } = useFormContext(); + + const onClick = () => { + const firstName = getValues(sourceContext.getSource('first_name')); + const lastName = getValues(sourceContext.getSource('last_name')); + const email = `${ + firstName ? firstName.toLowerCase() : '' + }.${lastName ? lastName.toLowerCase() : ''}@school.com`; + setValue(sourceContext.getSource('email'), email); + }; + + return ( + + ); +}; + +const StudentsInput = () => ( + + + + + + + + +); +``` + +{% endraw %} + +**Tip:** If you only need the item's index, you can leverage the [`useSimpleFormIteratorItem` hook](./SimpleFormIterator.md#getting-the-element-index) instead. diff --git a/docs/ReferenceOneInput.md b/docs/ReferenceOneInput.md index 3b152a0458..b7db80a8fc 100644 --- a/docs/ReferenceOneInput.md +++ b/docs/ReferenceOneInput.md @@ -234,58 +234,70 @@ Name of the field carrying the relationship on the referenced resource. For inst ``` -## Customizing The Child Inputs +## Limitations + +- `` cannot be used inside an `` or a ``. +- `` cannot have a `` or a `` as one of its children. +- `` does not support server side validation. + +## Changing An Item's Value Programmatically + +You can leverage `react-hook-form`'s [`setValue`](https://react-hook-form.com/docs/useform/setvalue) method to change the reference record's value programmatically. -`` works by cloning its children and overriding their `source` prop, to add a temporary field name prefix. This means that, if you need to nest your inputs inside another component, you will need to propagate the `source` prop to them. +However you need to know the `name` under which the inputs were registered in the form, and these names are dynamically generated by ``. -In this example, the `` component is wrapped inside a `` component. That adds an icon and additional styling. +To get the name of a specific input, you can leverage the `SourceContext` created by react-admin, which can be accessed using the `useSourceContext` hook. + +This context provides a `getSource` function that returns the effective `source` for an input in the current context, which you can use as input name for `setValue`. + +Here is an example where we leverage `getSource` and `setValue` to update some of the book details when the 'Update book details' button is clicked: {% raw %} + ```tsx -import AccountCircle from '@mui/icons-material/AccountCircle'; -import AutoStoriesIcon from '@mui/icons-material/AutoStories'; -import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; -import ClassIcon from '@mui/icons-material/Class'; -import LanguageIcon from '@mui/icons-material/Language'; -import { Box, SxProps } from '@mui/material'; -import * as React from 'react'; -import { TextInput } from 'react-admin'; +import { NumberInput, TextInput, useSourceContext } from 'react-admin'; import { ReferenceOneInput } from '@react-admin/ra-relationships'; - -const MyCustomInput = ({ - source, - icon: Icon, -}: { - source: string; - icon: React.FunctionComponent<{ sx?: SxProps }>; -}) => ( - - - - -); - -export const CustomInputs = () => ( - - - - - - +import { useFormContext } from 'react-hook-form'; +import { Button, Stack, Box } from '@mui/material'; + +const UpdateBookDetails = () => { + const sourceContext = useSourceContext(); + const { setValue } = useFormContext(); + + const onClick = () => { + // Generate random values for year and pages + const year = 1000 + Math.floor(Math.random() * 1000); + const pages = 100 + Math.floor(Math.random() * 900); + setValue(sourceContext.getSource('year'), year); + setValue(sourceContext.getSource('pages'), pages); + }; + + return ( + + ); +}; + +const BookDetails = () => ( + + + + + + + + + + + ); ``` -{% endraw %} - -![ReferenceOneInput with custom inputs](https://react-admin-ee.marmelab.com/assets/ra-relationships/latest/reference-one-input-custom-inputs.png) -## Limitations - -- `` cannot be used inside an `` or a ``. -- `` cannot have a `` or a `` as one of its children. -- `` does not support server side validation. +{% endraw %} diff --git a/docs/SimpleFormIterator.md b/docs/SimpleFormIterator.md index 64e4c615a5..7fc9425476 100644 --- a/docs/SimpleFormIterator.md +++ b/docs/SimpleFormIterator.md @@ -430,3 +430,44 @@ This property accepts the following subclasses: | `RaSimpleFormIterator-inline` | Applied to rows when `inline` is true | | `RaSimpleFormIterator-line` | Applied to each row | | `RaSimpleFormIterator-list` | Applied to the `
    ` element | + +## Getting The Element Index + +Inside a ``, you can access the index of the current element using the `useSimpleFormIteratorItem` hook. + +{% raw %} + +```tsx +import { + TextInput, + ArrayInput, + SimpleFormIterator, + useSimpleFormIteratorItem, +} from 'react-admin'; +import { Typography } from '@mui/material'; + +const IndexField = () => { + const { index } = useSimpleFormIteratorItem(); + return ( + + #{index + 1}: + + ); +}; + +const UserArray = () => ( + + + + + + + +); +``` + +{% endraw %} + +**Tip:** This hook also returns the total number of elements (`total`). + +**Tip:** If you need the index to change the value of an input programmatically, you should use the [`useSourceContext` hook](./ArrayInput.md#changing-an-items-value-programmatically) instead. diff --git a/docs/TranslatableInputs.md b/docs/TranslatableInputs.md index c604b74be2..df8ac3aa6f 100644 --- a/docs/TranslatableInputs.md +++ b/docs/TranslatableInputs.md @@ -198,3 +198,53 @@ You can add validators to any of the inputs inside a `TranslatableInputs`. If an ``` + +## Changing The Value Programmatically + +You can leverage `react-hook-form`'s [`setValue`](https://react-hook-form.com/docs/useform/setvalue) method to change an input's value programmatically. + +However you need to know the `name` under which the input was registered in the form, and this name is dynamically generated depending on the locale. + +To get the name of the input for a given locale, you can leverage the `SourceContext` created by react-admin, which can be accessed using the `useSourceContext` hook. + +This context provides a `getSource` function that returns the effective `source` for an input in the current context, which you can use as input name for `setValue`. + +Here is an example where we leverage `getSource` and `setValue` to pre-fill the 'description' input using the value of the 'title' input when the corresponding button is clicked: + +{% raw %} + +```tsx +import { TranslatableInputs, TextInput, useSourceContext } from 'react-admin'; +import { useFormContext } from 'react-hook-form'; +import { Button } from '@mui/material'; + +const PrefillWithTitleButton = () => { + const sourceContext = useSourceContext(); + const { setValue, getValues } = useFormContext(); + + const onClick = () => { + setValue( + // sourceContext.getSource('description') will for instance return + // 'description.en' + sourceContext.getSource('description'), + getValues(sourceContext.getSource('title')) + ); + }; + + return ( + + ); +}; + +const MyInputs = () => ( + + + + + +); +``` + +{% endraw %} diff --git a/docs/Upgrade.md b/docs/Upgrade.md index 4fa42ee457..377e8399af 100644 --- a/docs/Upgrade.md +++ b/docs/Upgrade.md @@ -975,6 +975,47 @@ const PostEdit = () => ( ); ``` +If you still need to access the *effective* source of an input inside an ``, for example to change the value programmatically using `setValue`, you will need to leverage the [`useSourceContext` hook](./ArrayInput#changing-an-items-value-programmatically). + +{% raw %} + +```tsx +import { ArrayInput, SimpleFormIterator, TextInput, useSourceContext } from 'react-admin'; +import { useFormContext } from 'react-hook-form'; +import { Button } from '@mui/material'; + +const MakeAdminButton = () => { + const sourceContext = useSourceContext(); + const { setValue } = useFormContext(); + + const onClick = () => { + // sourceContext.getSource('role') will for instance return + // 'users.0.role' + setValue(sourceContext.getSource('role'), 'admin'); + }; + + return ( + + ); +}; + +const UserArray = () => ( + + + + + + + +); +``` + +{% endraw %} + +**Tip:** If you only need the item's index, you can leverage the [`useSimpleFormIteratorItem` hook](./SimpleFormIterator.md#getting-the-element-index) instead. + ### Mutation Middlewares No Longer Receive The Mutation Options Mutations middlewares no longer receive the mutation options: diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx index 27f8d29b1e..d775191e67 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput/ArrayInput.stories.tsx @@ -6,8 +6,9 @@ import { Resource, testI18nProvider, TestMemoryRouter, + useSourceContext, } from 'ra-core'; -import { InputAdornment } from '@mui/material'; +import { Button, InputAdornment } from '@mui/material'; import { Edit, Create } from '../../detail'; import { SimpleForm, TabbedForm } from '../../form'; @@ -884,3 +885,42 @@ export const WithReferenceField = () => ( ); + +const MakeAdminButton = () => { + const sourceContext = useSourceContext(); + const { setValue } = useFormContext(); + + const onClick = () => { + setValue(sourceContext.getSource('role'), 'admin'); + }; + + return ( + + ); +}; + +const BookEditSetValue = () => { + return ( + + + + + + + + + + + + ); +}; + +export const SetValue = () => ( + + + + + +); diff --git a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.stories.tsx b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.stories.tsx index 2f61d77d02..03505e4d6f 100644 --- a/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.stories.tsx +++ b/packages/ra-ui-materialui/src/input/ArrayInput/SimpleFormIterator.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Button } from '@mui/material'; +import { Button, Typography } from '@mui/material'; import { Edit } from '../../detail'; import { SimpleForm } from '../../form'; @@ -8,6 +8,7 @@ import { SimpleFormIterator } from './SimpleFormIterator'; import { TextInput } from '../TextInput'; import { AdminContext } from '../../AdminContext'; import { defaultTheme } from '../../theme/defaultTheme'; +import { useSimpleFormIteratorItem } from './useSimpleFormIteratorItem'; export default { title: 'ra-ui-materialui/input/SimpleFormIterator' }; @@ -211,3 +212,22 @@ export const Theming = () => ( ); + +const IndexField = () => { + const { index } = useSimpleFormIteratorItem(); + return ( + + #{index + 1}: + + ); +}; + +export const UseSimpleFormIteratorItem = () => ( + + + + + + + +); diff --git a/packages/ra-ui-materialui/src/input/TranslatableInputs.stories.tsx b/packages/ra-ui-materialui/src/input/TranslatableInputs.stories.tsx index 8fbc627e17..cf55bb99ed 100644 --- a/packages/ra-ui-materialui/src/input/TranslatableInputs.stories.tsx +++ b/packages/ra-ui-materialui/src/input/TranslatableInputs.stories.tsx @@ -8,6 +8,9 @@ import { SimpleForm } from '../form'; import { TranslatableInputs } from './TranslatableInputs'; import { FormInspector } from './common'; import { TextInput } from './TextInput'; +import { useSourceContext } from 'ra-core'; +import { useFormContext } from 'react-hook-form'; +import { Button } from '@mui/material'; export default { title: 'ra-ui-materialui/input/TranslatableInputs' }; @@ -69,7 +72,36 @@ const Wrapper = ({ children }) => ( {children} + ); + +const PrefillWithTitleButton = () => { + const sourceContext = useSourceContext(); + const { setValue, getValues } = useFormContext(); + + const onClick = () => { + setValue( + sourceContext.getSource('description'), + getValues(sourceContext.getSource('title')) + ); + }; + + return ( + + ); +}; + +export const SetValue = () => ( + + + + + + + +);