|
| 1 | +--- |
| 2 | +title: <FormDataConsumer> |
| 3 | +--- |
| 4 | + |
| 5 | +Edition forms often contain linked inputs, e.g. country and city (the choices of the latter depending on the value of the former). |
| 6 | + |
| 7 | +The `<FormDataConsumer>` component gets the current (edited) values of the record and passes it to a child function. |
| 8 | + |
| 9 | +## Usage |
| 10 | + |
| 11 | +As <FormDataConsumer> uses the render props pattern, you can avoid creating an intermediate component like the <CityInput> component above: |
| 12 | + |
| 13 | +```tsx |
| 14 | +import * as React from 'react'; |
| 15 | +import { Edit, SimpleForm, SelectInput, FormDataConsumer } from 'react-admin'; |
| 16 | + |
| 17 | +const countries = ['USA', 'UK', 'France']; |
| 18 | +const cities: Record<string, string[]> = { |
| 19 | + USA: ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Phoenix'], |
| 20 | + UK: ['London', 'Birmingham', 'Glasgow', 'Liverpool', 'Bristol'], |
| 21 | + France: ['Paris', 'Marseille', 'Lyon', 'Toulouse', 'Nice'], |
| 22 | +}; |
| 23 | +const toChoices = (items: string[]) => |
| 24 | + items.map(item => ({ id: item, name: item })); |
| 25 | + |
| 26 | +const OrderEdit = () => ( |
| 27 | + <Edit> |
| 28 | + <SimpleForm> |
| 29 | + <SelectInput source="country" choices={toChoices(countries)} /> |
| 30 | + <FormDataConsumer<{ country: string }>> |
| 31 | + {({ formData, ...rest }) => ( |
| 32 | + <SelectInput |
| 33 | + source="cities" |
| 34 | + choices={ |
| 35 | + formData.country |
| 36 | + ? toChoices(cities[formData.country]) |
| 37 | + : [] |
| 38 | + } |
| 39 | + {...rest} |
| 40 | + /> |
| 41 | + )} |
| 42 | + </FormDataConsumer> |
| 43 | + </SimpleForm> |
| 44 | + </Edit> |
| 45 | +); |
| 46 | +``` |
| 47 | + |
| 48 | +## Hiding Inputs Based On Other Inputs |
| 49 | + |
| 50 | +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. |
| 51 | + |
| 52 | +For such cases, you can use the approach described above, using the `<FormDataConsumer>` component. |
| 53 | + |
| 54 | +```tsx |
| 55 | +import { FormDataConsumer } from 'react-admin'; |
| 56 | + |
| 57 | +const PostEdit = () => ( |
| 58 | + <Edit> |
| 59 | + <SimpleForm shouldUnregister> |
| 60 | + <BooleanInput source="hasEmail" /> |
| 61 | + <FormDataConsumer<{ hasEmail: boolean }>> |
| 62 | + {({ formData, ...rest }) => |
| 63 | + formData.hasEmail && <TextInput source="email" {...rest} /> |
| 64 | + } |
| 65 | + </FormDataConsumer> |
| 66 | + </SimpleForm> |
| 67 | + </Edit> |
| 68 | +); |
| 69 | +``` |
| 70 | + |
| 71 | +:::note |
| 72 | +By default, `react-hook-form` submits values of unmounted input components. In the above example, the `shouldUnregister` prop of the `<SimpleForm>` component prevents that from happening. That way, when end users hide an input, its value isn’t included in the submitted data. |
| 73 | +::: |
| 74 | + |
| 75 | +:::note |
| 76 | +`shouldUnregister` should be avoided when using `<ArrayInput>` (which internally uses `useFieldArray`) as the unregister function gets called after input unmount/remount and reorder. This limitation is mentioned in the `react-hook-form` [documentation](https://react-hook-form.com/docs/usecontroller#props). If you are in such a situation, you can use the [`transform`](./EditBase.md#transform) prop to manually clean the submitted values. |
| 77 | +::: |
| 78 | + |
| 79 | +## Usage inside an ArrayInput |
| 80 | + |
| 81 | +When used inside an `<ArrayInput>`, `<FormDataConsumer>` 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 `<SimpleFormIterator>`, as in the following example: |
| 82 | + |
| 83 | +```tsx |
| 84 | +import { FormDataConsumer } from 'react-admin'; |
| 85 | + |
| 86 | +const PostEdit = () => ( |
| 87 | + <Edit> |
| 88 | + <SimpleForm> |
| 89 | + <ArrayInput source="authors"> |
| 90 | + <SimpleFormIterator> |
| 91 | + <TextInput source="name" /> |
| 92 | + <FormDataConsumer<{ name: string }>> |
| 93 | + {({ |
| 94 | + formData, // The whole form data |
| 95 | + scopedFormData, // The data for this item of the ArrayInput |
| 96 | + ...rest |
| 97 | + }) => |
| 98 | + scopedFormData && scopedFormData.name ? ( |
| 99 | + <SelectInput |
| 100 | + source="role" // Will translate to "authors[0].role" |
| 101 | + choices={[ |
| 102 | + { id: 1, name: 'Head Writer' }, |
| 103 | + { id: 2, name: 'Co-Writer' }, |
| 104 | + ]} |
| 105 | + {...rest} |
| 106 | + /> |
| 107 | + ) : null |
| 108 | + } |
| 109 | + </FormDataConsumer> |
| 110 | + </SimpleFormIterator> |
| 111 | + </ArrayInput> |
| 112 | + </SimpleForm> |
| 113 | + </Edit> |
| 114 | +); |
| 115 | +``` |
| 116 | + |
| 117 | +:::tip |
| 118 | +TypeScript users will notice that scopedFormData is typed as an optional parameter. This is because the `<FormDataConsumer>` component can be used outside of an `<ArrayInput>` and in that case, this parameter will be undefined. If you are inside an `<ArrayInput>`, you can safely assume that this parameter will be defined. |
| 119 | +::: |
| 120 | + |
| 121 | +:::tip |
| 122 | +If you need to access the effective source of an input inside an `<ArrayInput>`, for example to change the value programmatically using setValue, you will need to leverage the [`useSourceContext`](./useSourceContext.md) hook. |
| 123 | +::: |
| 124 | + |
| 125 | +@todo: add useSourceContext Page |
| 126 | + |
| 127 | +## Props |
| 128 | + |
| 129 | +| Prop | Required | Type | Default | Description | |
| 130 | +| ---------- | -------- | ---------- | ------- | ----------------------------------------------------------------- | |
| 131 | +| `children` | Required | `function` | - | A function that takes the `formData` and returns a `ReactElement` | |
| 132 | + |
| 133 | +## `children` |
| 134 | + |
| 135 | +The function used to render a component based on the `formData`. |
| 136 | + |
| 137 | +```tsx |
| 138 | +<FormDataConsumer<{ name: string }>> |
| 139 | + {({ |
| 140 | + formData, // The whole form data |
| 141 | + scopedFormData, // The data for this item of the ArrayInput |
| 142 | + }) => { |
| 143 | + /* ... */ |
| 144 | + }} |
| 145 | +</FormDataConsumer> |
| 146 | +``` |
0 commit comments