Skip to content

Commit 68e64d4

Browse files
committed
update create-edit
1 parent fb73b7c commit 68e64d4

File tree

13 files changed

+1281
-805
lines changed

13 files changed

+1281
-805
lines changed

docs_headless/src/content/docs/create-edit/CreateBase.md

Lines changed: 365 additions & 33 deletions
Large diffs are not rendered by default.

docs_headless/src/content/docs/create-edit/EditBase.md

Lines changed: 224 additions & 126 deletions
Large diffs are not rendered by default.

docs_headless/src/content/docs/create-edit/EditTutorial.md

Lines changed: 326 additions & 240 deletions
Large diffs are not rendered by default.
Lines changed: 69 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
---
2-
layout: default
3-
title: "Form"
4-
storybook_path: ra-core-form-form--basic
2+
title: "<Form>"
53
---
64

7-
# `<Form>`
8-
95
`<Form>` is a headless component that creates a `<form>` to edit a record, and renders its children. Use it to build a custom form layout, or to use another UI kit than Material UI.
106

117
`<Form>` reads the `record` from the `RecordContext`, uses it to initialize the defaultValues of a react-hook-form via `useForm`, turns the `validate` function info a react-hook-form compatible form validator, notifies the user when the input validation fails, and creates a form context via `<FormProvider>`.
@@ -14,31 +10,31 @@ storybook_path: ra-core-form-form--basic
1410

1511
## Usage
1612

17-
Use `<Form>` to build completely custom form layouts. Don't forget to include a submit button (or react-admin's [`<SaveButton>`](./SaveButton.md)) to actually save the record.
13+
Use `<Form>` to build completely custom form layouts. Don't forget to include a submit button to actually save the record.
1814

1915
```jsx
20-
import { Create, Form, TextInput, RichTextInput, SaveButton } from 'react-admin';
21-
import { Grid } from '@mui/material';
16+
import { CreateBase, Form } from 'ra-core';
17+
import { TextInput } from './TextInput';
2218

2319
export const PostCreate = () => (
24-
<Create>
20+
<CreateBase>
2521
<Form>
26-
<Grid container>
27-
<Grid item xs={6}>
22+
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
23+
<div>
2824
<TextInput source="title" />
29-
</Grid>
30-
<Grid item xs={6}>
25+
</div>
26+
<div>
3127
<TextInput source="author" />
32-
</Grid>
33-
<Grid item xs={12}>
34-
<RichTextInput source="body" />
35-
</Grid>
36-
<Grid item xs={12}>
37-
<SaveButton />
38-
</Grid>
39-
</Grid>
28+
</div>
29+
<div style={{ gridColumn: 'span 2' }}>
30+
<TextInput source="body" multiline />
31+
</div>
32+
<div style={{ gridColumn: 'span 2' }}>
33+
<button type="submit">Save</button>
34+
</div>
35+
</div>
4036
</Form>
41-
</Create>
37+
</CreateBase>
4238
);
4339
```
4440

@@ -66,25 +62,28 @@ Additional props are passed to [the `useForm` hook](https://react-hook-form.com/
6662
The value of the form `defaultValues` prop is an object, or a function returning an object, specifying default values for the created record. For instance:
6763

6864
```jsx
65+
import { CreateBase, Form } from 'ra-core';
66+
import { TextInput, RichTextInput, NumberInput } from './inputs';
67+
6968
const postDefaultValue = () => ({ id: uuid(), created_at: new Date(), nb_views: 0 });
7069

7170
export const PostCreate = () => (
72-
<Create>
71+
<CreateBase>
7372
<Form defaultValues={postDefaultValue}>
74-
<Stack>
73+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', padding: '1rem' }}>
7574
<TextInput source="title" />
7675
<RichTextInput source="body" />
7776
<NumberInput source="nb_views" />
78-
<SaveButton />
79-
</Stack>
77+
<button type="submit">Save</button>
78+
</div>
8079
</Form>
81-
</Create>
80+
</CreateBase>
8281
);
8382
```
8483

8584
**Tip**: You can include properties in the form `defaultValues` that are not listed as input components, like the `created_at` property in the previous example.
8685

87-
**Tip**: React-admin also allows to define default values at the input level. See the [Setting default Values](./Forms.md#default-values) section.
86+
**Tip**: React-admin also allows to define default values at the input level. See the [Setting default Values](../guides/Form.md#default-values) section.
8887

8988
## `id`
9089

@@ -93,17 +92,20 @@ Normally, a submit button only works when placed inside a `<form>` tag. However,
9392
Set this form `id` via the `id` prop.
9493

9594
```jsx
95+
import { CreateBase, Form } from 'ra-core';
96+
import { TextInput, RichTextInput, NumberInput } from './inputs';
97+
9698
export const PostCreate = () => (
97-
<Create>
99+
<CreateBase>
98100
<Form defaultValues={postDefaultValue} id="post_create_form">
99-
<Stack>
101+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', padding: '1rem' }}>
100102
<TextInput source="title" />
101103
<RichTextInput source="body" />
102104
<NumberInput source="nb_views" />
103-
</Stack>
105+
</div>
104106
</Form>
105-
<SaveButton form="post_create_form" />
106-
</Create>
107+
<button type="submit" form="post_create_form">Save</button>
108+
</CreateBase>
107109
);
108110
```
109111

@@ -112,12 +114,14 @@ export const PostCreate = () => (
112114
The `<form novalidate>` attribute prevents the browser from validating the form. This is useful if you don't want to use the browser's default validation, or if you want to customize the error messages. To set this attribute on the underlying `<form>` tag, set the `noValidate` prop to `true`.
113115

114116
```jsx
117+
import { CreateBase, Form } from 'ra-core';
118+
115119
const PostCreate = () => (
116-
<Create>
120+
<CreateBase>
117121
<Form noValidate>
118122
...
119123
</Form>
120-
</Create>
124+
</CreateBase>
121125
);
122126
```
123127

@@ -126,17 +130,19 @@ const PostCreate = () => (
126130
By default, the `<Form>` calls the `save` callback passed to it by the edit or create controller, via the `SaveContext`. You can override this behavior by setting a callback as the `onSubmit` prop manually.
127131

128132
```jsx
133+
import { CreateBase, Form, useCreate } from 'ra-core';
134+
129135
export const PostCreate = () => {
130136
const [create] = useCreate();
131137
const postSave = (data) => {
132138
create('posts', { data });
133139
};
134140
return (
135-
<Create>
141+
<CreateBase>
136142
<Form onSubmit={postSave}>
137143
...
138144
</Form>
139-
</Create>
145+
</CreateBase>
140146
);
141147
};
142148
```
@@ -160,12 +166,14 @@ But for your own input components based on react-hook-form, this is not the defa
160166
If you prefer to omit the keys for empty values, set the `sanitizeEmptyValues` prop to `true`. This will sanitize the form data before passing it to the `dataProvider`, i.e. remove empty strings from the form state, unless the record actually had a value for that field before edition.
161167

162168
```jsx
169+
import { CreateBase, Form } from 'ra-core';
170+
163171
const PostCreate = () => (
164-
<Create>
172+
<CreateBase>
165173
<Form sanitizeEmptyValues>
166174
...
167175
</Form>
168-
</Create>
176+
</CreateBase>
169177
);
170178
```
171179

@@ -178,17 +186,18 @@ For the previous example, the data sent to the `dataProvider` will be:
178186
}
179187
```
180188

181-
**Note:** Setting the `sanitizeEmptyValues` prop to `true` will also have a (minor) impact on react-admin inputs (like `<TextInput>`, `<NumberInput>`, etc.): empty values (i.e. values equal to `null`) will be removed from the form state on submit, unless the record actually had a value for that field.
189+
**Note** Even with `sanitizeEmptyValues` set to `true`, deeply nested fields won't be set to `null` nor removed. If you need to sanitize those fields, use [the `transform` prop](./EditBase.md#transform) of `<EditBase>` or `<CreateBase>` components.
182190

183-
**Note** Even with `sanitizeEmptyValues` set to `true`, deeply nested fields won't be set to `null` nor removed. If you need to sanitize those fields, use [the `transform` prop](./Edit.md#transform) of `<Edit>` or `<Create>` components.
184-
185-
If you need a more fine-grained control over the sanitization, you can use [the `transform` prop](./Edit.md#transform) of `<Edit>` or `<Create>` components, or [the `parse` prop](./Inputs.md#parse) of individual inputs.
191+
If you need a more fine-grained control over the sanitization, you can use [the `transform` prop](./EditBase.md#transform) of `<EditBase>` or `<CreateBase>` components, or [the `parse` prop](../inputs/useInput.md#parse) of individual inputs.
186192

187193
## `validate`
188194

189195
The value of the form `validate` prop must be a function taking the record as input, and returning an object with error messages indexed by field. For instance:
190196

191197
```jsx
198+
import { CreateBase, Form } from 'ra-core';
199+
import { TextInput } from './TextInput';
200+
192201
const validateUserCreation = (values) => {
193202
const errors = {};
194203
if (!values.firstName) {
@@ -208,12 +217,12 @@ const validateUserCreation = (values) => {
208217
};
209218

210219
export const UserCreate = () => (
211-
<Create>
220+
<CreateBase>
212221
<Form validate={validateUserCreation}>
213222
<TextInput label="First Name" source="firstName" />
214223
<TextInput label="Age" source="age" />
215224
</Form>
216-
</Create>
225+
</CreateBase>
217226
);
218227
```
219228

@@ -230,12 +239,14 @@ React-admin keeps track of the form state, so it can detect when the user leaves
230239
Warning about unsaved changes is an opt-in feature: you must set the `warnWhenUnsavedChanges` prop in the form component to enable it:
231240

232241
```jsx
242+
import { EditBase, Form } from 'ra-core';
243+
233244
export const TagEdit = () => (
234-
<Edit>
245+
<EditBase>
235246
<Form warnWhenUnsavedChanges>
236247
...
237248
</Form>
238-
</Edit>
249+
</EditBase>
239250
);
240251
```
241252

@@ -252,75 +263,6 @@ const { isDirty } = useFormState(); // ✅
252263
const formState = useFormState(); // ❌ should deconstruct the formState
253264
```
254265

255-
## AutoSave
256-
257-
In forms where users may spend a lot of time, it's a good idea to save the form automatically after a few seconds of inactivity. You can auto save the form content by using [the `<AutoSave>` component](./AutoSave.md).
258-
259-
<video controls autoplay playsinline muted loop>
260-
<source src="../img/AutoSave.webm" type="video/webm"/>
261-
<source src="../img/AutoSave.mp4" type="video/mp4"/>
262-
Your browser does not support the video tag.
263-
</video>
264-
265-
{% raw %}
266-
```tsx
267-
import { AutoSave } from '@react-admin/ra-form-layout';
268-
import { Edit, Form, TextInput, DateInput, SelectInput } from 'react-admin';
269-
import { Stack } from '@mui/material';
270-
271-
const PersonEdit = () => (
272-
<Edit mutationMode="optimistic">
273-
<Form resetOptions={{ keepDirtyValues: true }}>
274-
<Stack>
275-
<TextInput source="first_name" />
276-
<TextInput source="last_name" />
277-
<DateInput source="dob" />
278-
<SelectInput source="sex" choices={[
279-
{ id: 'male', name: 'Male' },
280-
{ id: 'female', name: 'Female' },
281-
]}/>
282-
</Stack>
283-
<AutoSave />
284-
</Form>
285-
</Edit>
286-
);
287-
```
288-
{% endraw %}
289-
290-
Note that you **must** set the `<Form resetOptions>` prop to `{ keepDirtyValues: true }`. If you forget that prop, any change entered by the end user after the autosave but before its acknowledgement by the server will be lost.
291-
292-
If you're using it in an `<Edit>` page, you must also use a `pessimistic` or `optimistic` [`mutationMode`](https://marmelab.com/react-admin/Edit.html#mutationmode) - `<AutoSave>` doesn't work with the default `mutationMode="undoable"`.
293-
294-
Check [the `<AutoSave>` component](./AutoSave.md) documentation for more details.
295-
296-
An alternative to the `<AutoSave>` component is to use [the `<AutoPersistInStore>` component](./AutoPersistInStore.md). This component saves the form values in the local storage of the browser. This way, if the user navigates away without saving, the form values are reapplied when the user comes back to the page. This is useful for long forms where users may spend a lot of time.
297-
298-
<video controls autoplay playsinline muted loop>
299-
<source src="../img/AutoPersistInStore.mp4" type="video/mp4"/>
300-
Your browser does not support the video tag.
301-
</video>
302-
303-
To enable this behavior, add the `<AutoPersistInStore>` component inside the form component:
304-
305-
```tsx
306-
import { AutoPersistInStore } from '@react-admin/ra-form-layout';
307-
import { Edit, Form, TextInput } from 'react-admin';
308-
309-
const PostEdit = () => (
310-
<Edit>
311-
<Form>
312-
<Stack>
313-
<TextInput source="title" />
314-
<TextInput source="teaser" />
315-
</Stack>
316-
<AutoPersistInStore />
317-
</Form>
318-
</Edit>
319-
);
320-
```
321-
322-
Check [the `<AutoPersistInStore>` component](./AutoPersistInStore.md) documentation for more details.
323-
324266
## Linking Two Inputs
325267

326268
<iframe src="https://www.youtube-nocookie.com/embed/YkqjydtmfcU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen style="aspect-ratio: 16 / 9;width:100%;margin-bottom:1em;"></iframe>
@@ -331,7 +273,8 @@ React-admin relies on [react-hook-form](https://react-hook-form.com/) for form h
331273

332274
```jsx
333275
import * as React from 'react';
334-
import { Edit, SimpleForm, SelectInput } from 'react-admin';
276+
import { EditBase, Form } from 'ra-core';
277+
import { SelectInput } from './SelectInput';
335278
import { useWatch } from 'react-hook-form';
336279

337280
const countries = ['USA', 'UK', 'France'];
@@ -353,15 +296,17 @@ const CityInput = () => {
353296
};
354297

355298
const OrderEdit = () => (
356-
<Edit>
357-
<SimpleForm>
358-
<SelectInput source="country" choices={toChoices(countries)} />
359-
<CityInput />
360-
</SimpleForm>
361-
</Edit>
299+
<EditBase>
300+
<Form>
301+
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', padding: '1rem' }}>
302+
<SelectInput source="country" choices={toChoices(countries)} />
303+
<CityInput />
304+
</div>
305+
</Form>
306+
</EditBase>
362307
);
363308

364309
export default OrderEdit;
365310
```
366311

367-
**Tip:** If you'd like to avoid creating an intermediate component like `<CityInput>`, or are using an `<ArrayInput>`, you can use the [`<FormDataConsumer>`](./Inputs.md#linking-two-inputs) component as an alternative.
312+
**Tip:** If you'd like to avoid creating an intermediate component like `<CityInput>`, or are using an `<ArrayInput>`, you can use the [`<FormDataConsumer>`](../inputs/Inputs.md#linking-two-inputs) component as an alternative.

0 commit comments

Comments
 (0)