@conform-to/react 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -295
- package/helpers.d.ts +19 -6
- package/helpers.js +21 -14
- package/hooks.d.ts +22 -28
- package/hooks.js +109 -127
- package/index.d.ts +1 -1
- package/index.js +16 -0
- package/module/helpers.js +21 -14
- package/module/hooks.js +110 -128
- package/module/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,19 +20,25 @@
|
|
|
20
20
|
|
|
21
21
|
### useForm
|
|
22
22
|
|
|
23
|
-
By default, the browser calls the [reportValidity()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reportValidity) API on the form element when
|
|
23
|
+
By default, the browser calls the [reportValidity()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reportValidity) API on the form element when a submission is triggered. This checks the validity of all the fields and reports through the error bubbles.
|
|
24
24
|
|
|
25
|
-
This hook enhances the form validation behaviour
|
|
25
|
+
This hook enhances the form validation behaviour by:
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
- Enabling customizing form validation behaviour.
|
|
28
|
+
- Capturing the error message and removes the error bubbles.
|
|
29
|
+
- Preparing all properties required to configure the dom elements.
|
|
30
30
|
|
|
31
31
|
```tsx
|
|
32
32
|
import { useForm } from '@conform-to/react';
|
|
33
33
|
|
|
34
34
|
function LoginForm() {
|
|
35
|
-
const form = useForm({
|
|
35
|
+
const [form, { email, password }] = useForm({
|
|
36
|
+
/**
|
|
37
|
+
* If the form id is provided, Id for label,
|
|
38
|
+
* input and error elements will be derived.
|
|
39
|
+
*/
|
|
40
|
+
id: undefined,
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* Validation mode.
|
|
38
44
|
* Support "client-only" or "server-validation".
|
|
@@ -59,6 +65,11 @@ function LoginForm() {
|
|
|
59
65
|
*/
|
|
60
66
|
state: undefined;
|
|
61
67
|
|
|
68
|
+
/**
|
|
69
|
+
* An object describing the constraint of each field
|
|
70
|
+
*/
|
|
71
|
+
constraint: undefined;
|
|
72
|
+
|
|
62
73
|
/**
|
|
63
74
|
* Enable native validation before hydation.
|
|
64
75
|
*
|
|
@@ -77,14 +88,14 @@ function LoginForm() {
|
|
|
77
88
|
* A function to be called when the form should be (re)validated.
|
|
78
89
|
* Only sync validation is supported
|
|
79
90
|
*/
|
|
80
|
-
onValidate({ form, formData
|
|
91
|
+
onValidate({ form, formData }) {
|
|
81
92
|
// ...
|
|
82
93
|
},
|
|
83
94
|
|
|
84
95
|
/**
|
|
85
96
|
* The submit event handler of the form.
|
|
86
97
|
*/
|
|
87
|
-
onSubmit(event, {
|
|
98
|
+
onSubmit(event, { formData, submission }) {
|
|
88
99
|
// ...
|
|
89
100
|
},
|
|
90
101
|
});
|
|
@@ -96,15 +107,16 @@ function LoginForm() {
|
|
|
96
107
|
<details>
|
|
97
108
|
<summary>What is `form.props`?</summary>
|
|
98
109
|
|
|
99
|
-
It is a group of properties
|
|
110
|
+
It is a group of properties required to hook into form events. They can also be set explicitly as shown below:
|
|
100
111
|
|
|
101
112
|
```tsx
|
|
102
113
|
function RandomForm() {
|
|
103
|
-
const form = useForm();
|
|
114
|
+
const [form] = useForm();
|
|
104
115
|
|
|
105
116
|
return (
|
|
106
117
|
<form
|
|
107
118
|
ref={form.props.ref}
|
|
119
|
+
id={form.props.id}
|
|
108
120
|
onSubmit={form.props.onSubmit}
|
|
109
121
|
noValidate={form.props.noValidate}
|
|
110
122
|
>
|
|
@@ -126,7 +138,7 @@ import { useFrom } from '@conform-to/react';
|
|
|
126
138
|
import { Form } from '@remix-run/react';
|
|
127
139
|
|
|
128
140
|
function LoginForm() {
|
|
129
|
-
const form = useForm();
|
|
141
|
+
const [form] = useForm();
|
|
130
142
|
|
|
131
143
|
return (
|
|
132
144
|
<Form method="post" action="/login" {...form.props}>
|
|
@@ -138,157 +150,57 @@ function LoginForm() {
|
|
|
138
150
|
|
|
139
151
|
</details>
|
|
140
152
|
|
|
141
|
-
<details>
|
|
142
|
-
<summary>Is the `onValidate` function required?</summary>
|
|
143
|
-
|
|
144
|
-
The `onValidate` function is not required if the validation logic can be fully covered by the [native constraints](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Constraint_validation#validation-related_attributes), e.g. **required** / **min** / **pattern** etc.
|
|
145
|
-
|
|
146
|
-
```tsx
|
|
147
|
-
import { useForm, useFieldset } from '@conform-to/react';
|
|
148
|
-
|
|
149
|
-
function LoginForm() {
|
|
150
|
-
const formProps = useForm();
|
|
151
|
-
const { email, password } = useFieldset(formProps.ref);
|
|
152
|
-
|
|
153
|
-
return (
|
|
154
|
-
<form {...formProps}>
|
|
155
|
-
<label>
|
|
156
|
-
<input type="email" name="email" required />
|
|
157
|
-
{email.error}
|
|
158
|
-
</label>
|
|
159
|
-
<label>
|
|
160
|
-
<input type="password" name="password" required />
|
|
161
|
-
{password.error}
|
|
162
|
-
</label>
|
|
163
|
-
<button type="submit">Login</button>
|
|
164
|
-
</form>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
</details>
|
|
170
|
-
|
|
171
153
|
---
|
|
172
154
|
|
|
173
155
|
### useFieldset
|
|
174
156
|
|
|
175
|
-
This hook
|
|
176
|
-
|
|
177
|
-
1. Capturing errors at the form/fieldset level, removing the need to setup invalid handler on each field.
|
|
178
|
-
2. Defining config in one central place. e.g. name, default value and constraint, then distributing it to each field with the [conform](#conform) helpers.
|
|
157
|
+
This hook enables you to work with [nested object](/docs/configuration.md#nested-object) by monitoring the state of each nested field and prepraing the config required.
|
|
179
158
|
|
|
180
159
|
```tsx
|
|
181
|
-
import { useForm, useFieldset } from '@conform-to/react';
|
|
160
|
+
import { useForm, useFieldset, conform } from '@conform-to/react';
|
|
182
161
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
};
|
|
162
|
+
interface Address {
|
|
163
|
+
street: string;
|
|
164
|
+
zipcode: string;
|
|
165
|
+
city: string;
|
|
166
|
+
country: string;
|
|
167
|
+
}
|
|
190
168
|
|
|
191
|
-
function
|
|
192
|
-
const
|
|
193
|
-
const {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
*/
|
|
197
|
-
formProps.ref,
|
|
198
|
-
{
|
|
199
|
-
/**
|
|
200
|
-
* The prefix used to generate the name of nested fields.
|
|
201
|
-
*/
|
|
202
|
-
name: 'book',
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* An object representing the initial value of the fieldset.
|
|
206
|
-
*/
|
|
207
|
-
defaultValue: {
|
|
208
|
-
isbn: '0340013818',
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* An object describing the initial error of each field
|
|
213
|
-
*/
|
|
214
|
-
initialError: {
|
|
215
|
-
isbn: 'Invalid ISBN',
|
|
216
|
-
},
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* An object describing the constraint of each field
|
|
220
|
-
*/
|
|
221
|
-
constraint: {
|
|
222
|
-
isbn: {
|
|
223
|
-
required: true,
|
|
224
|
-
pattern: '[0-9]{10,13}',
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* The id of the form. This is required only if you
|
|
230
|
-
* are connecting each field to a form remotely.
|
|
231
|
-
*/
|
|
232
|
-
form: 'remote-form-id',
|
|
233
|
-
},
|
|
169
|
+
function Example() {
|
|
170
|
+
const [form, { address }] = useForm<{ address: Address }>();
|
|
171
|
+
const { city, zipcode, street, country } = useFieldset(
|
|
172
|
+
form.ref,
|
|
173
|
+
address.config,
|
|
234
174
|
);
|
|
235
175
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
*/
|
|
253
|
-
console.log(isbn.config.defaultValue);
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Initial error message
|
|
257
|
-
* This would be 'Invalid ISBN' if specified
|
|
258
|
-
*/
|
|
259
|
-
console.log(isbn.config.initialError);
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* This would be `random-form-id`
|
|
263
|
-
* because of the `form` option provided
|
|
264
|
-
*/
|
|
265
|
-
console.log(isbn.config.form);
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Constraint of the field (required, minLength etc)
|
|
269
|
-
*
|
|
270
|
-
* For example, the constraint of the isbn field would be:
|
|
271
|
-
* {
|
|
272
|
-
* required: true,
|
|
273
|
-
* pattern: '[0-9]{10,13}'
|
|
274
|
-
* }
|
|
275
|
-
*/
|
|
276
|
-
console.log(isbn.config.required);
|
|
277
|
-
console.log(isbn.config.pattern);
|
|
278
|
-
|
|
279
|
-
return <form {...formProps}>{/* ... */}</form>;
|
|
176
|
+
return (
|
|
177
|
+
<form {...form.props}>
|
|
178
|
+
<fieldset>
|
|
179
|
+
<legned>Address</legend>
|
|
180
|
+
<input {...conform.input(street.config)} />
|
|
181
|
+
<div>{street.error}</div>
|
|
182
|
+
<input {...conform.input(zipcode.config)} />
|
|
183
|
+
<div>{zipcode.error}</div>
|
|
184
|
+
<input {...conform.input(city.config)} />
|
|
185
|
+
<div>{city.error}</div>
|
|
186
|
+
<input {...conform.input(country.config)} />
|
|
187
|
+
<div>{country.error}</div>
|
|
188
|
+
</fieldset>
|
|
189
|
+
<button>Submit</button>
|
|
190
|
+
</form>
|
|
191
|
+
);
|
|
280
192
|
}
|
|
281
193
|
```
|
|
282
194
|
|
|
283
195
|
If you don't have direct access to the form ref, you can also pass a fieldset ref.
|
|
284
196
|
|
|
285
197
|
```tsx
|
|
286
|
-
import { useFieldset } from '@conform-to/react';
|
|
198
|
+
import { type FieldConfig, useFieldset } from '@conform-to/react';
|
|
287
199
|
import { useRef } from 'react';
|
|
288
200
|
|
|
289
|
-
function Fieldset() {
|
|
290
|
-
const ref = useRef();
|
|
291
|
-
const
|
|
201
|
+
function Fieldset(config: FieldConfig<Address>) {
|
|
202
|
+
const ref = useRef<HTMLFieldsetElement>(null);
|
|
203
|
+
const { city, zipcode, street, country } = useFieldset(ref, config);
|
|
292
204
|
|
|
293
205
|
return <fieldset ref={ref}>{/* ... */}</fieldset>;
|
|
294
206
|
}
|
|
@@ -297,7 +209,7 @@ function Fieldset() {
|
|
|
297
209
|
<details>
|
|
298
210
|
<summary>Why does `useFieldset` require a ref object of the form or fieldset?</summary>
|
|
299
211
|
|
|
300
|
-
|
|
212
|
+
**conform** utilises the DOM as its context provider / input registry, which maintains a link between each input / button / fieldset with the form through the [form property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#properties). The ref object allows it to restrict the scope to elements associated to the same form only.
|
|
301
213
|
|
|
302
214
|
```tsx
|
|
303
215
|
function ExampleForm() {
|
|
@@ -324,164 +236,84 @@ function ExampleForm() {
|
|
|
324
236
|
|
|
325
237
|
### useFieldList
|
|
326
238
|
|
|
327
|
-
|
|
239
|
+
This hook enables you to work with [array](/docs/configuration.md#array) and support [list](#list) command button builder to modify a list. It can also be used with [useFieldset](#usefieldset) for [nested list](/docs/configuration.md#nested-list) at the same time.
|
|
328
240
|
|
|
329
241
|
```tsx
|
|
330
|
-
import {
|
|
331
|
-
import { useRef } from 'react';
|
|
242
|
+
import { useForm, useFieldList, list } from '@conform-to/react';
|
|
332
243
|
|
|
333
244
|
/**
|
|
334
245
|
* Consider the schema as follow:
|
|
335
246
|
*/
|
|
336
|
-
type
|
|
337
|
-
|
|
338
|
-
isbn: string;
|
|
247
|
+
type Schema = {
|
|
248
|
+
items: string[];
|
|
339
249
|
};
|
|
340
250
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
function CollectionFieldset() {
|
|
346
|
-
const ref = useRef();
|
|
347
|
-
const { books } = useFieldset<Collection>(ref);
|
|
348
|
-
const [bookList, command] = useFieldList(ref, books.config);
|
|
251
|
+
function Example() {
|
|
252
|
+
const [form, { items }] = useForm<Schema>();
|
|
253
|
+
const list = useFieldList(form.ref, items.config);
|
|
349
254
|
|
|
350
255
|
return (
|
|
351
256
|
<fieldset ref={ref}>
|
|
352
|
-
{
|
|
353
|
-
<div key={
|
|
354
|
-
{/*
|
|
355
|
-
<input
|
|
356
|
-
name={`${book.config.name}.name`}
|
|
357
|
-
defaultValue={book.config.defaultValue.name}
|
|
358
|
-
/>
|
|
359
|
-
<input
|
|
360
|
-
name={`${book.config.name}.isbn`}
|
|
361
|
-
defaultValue={book.config.defaultValue.isbn}
|
|
362
|
-
/>
|
|
363
|
-
|
|
364
|
-
{/* To setup a delete button */}
|
|
365
|
-
<button {...command.remove({ index })}>Delete</button>
|
|
366
|
-
</div>
|
|
367
|
-
))}
|
|
257
|
+
{list.map((item, index) => (
|
|
258
|
+
<div key={item.key}>
|
|
259
|
+
{/* Setup an input per item */}
|
|
260
|
+
<input {...conform.input(item.config)} />
|
|
368
261
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
add
|
|
372
|
-
</button>
|
|
373
|
-
</fieldset>
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
This hook can also be used in combination with `useFieldset` to distribute the config:
|
|
379
|
-
|
|
380
|
-
```tsx
|
|
381
|
-
import { useForm, useFieldset, useFieldList } from '@conform-to/react';
|
|
382
|
-
import { useRef } from 'react';
|
|
383
|
-
|
|
384
|
-
function CollectionFieldset() {
|
|
385
|
-
const ref = useRef();
|
|
386
|
-
const { books } = useFieldset<Collection>(ref);
|
|
387
|
-
const [bookList, command] = useFieldList(ref, books.config);
|
|
262
|
+
{/* Error of each item */}
|
|
263
|
+
<span>{item.error}</span>
|
|
388
264
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
{bookList.map((book, index) => (
|
|
392
|
-
<div key={book.key}>
|
|
393
|
-
{/* `book.config` is a FieldConfig object similar to `books` */}
|
|
394
|
-
<BookFieldset {...book.config} />
|
|
395
|
-
|
|
396
|
-
{/* To setup a delete button */}
|
|
397
|
-
<button {...command.remove({ index })}>Delete</button>
|
|
265
|
+
{/* Setup a delete button (Note: It is `items` not `item`) */}
|
|
266
|
+
<button {...list.remove(items.config.name, { index })}>Delete</button>
|
|
398
267
|
</div>
|
|
399
268
|
))}
|
|
400
269
|
|
|
401
|
-
{/*
|
|
402
|
-
<button {...
|
|
270
|
+
{/* Setup a button that can append a new row with optional default value */}
|
|
271
|
+
<button {...list.append(items.config.name, { defaultValue: '' })}>
|
|
272
|
+
add
|
|
273
|
+
</button>
|
|
403
274
|
</fieldset>
|
|
404
275
|
);
|
|
405
276
|
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* This is basically the BookFieldset component from
|
|
409
|
-
* the `useFieldset` example, but setting all the
|
|
410
|
-
* options with the component props instead
|
|
411
|
-
*/
|
|
412
|
-
function BookFieldset({ name, form, defaultValue, error }) {
|
|
413
|
-
const ref = useRef();
|
|
414
|
-
const { name, isbn } = useFieldset(ref, {
|
|
415
|
-
name,
|
|
416
|
-
form,
|
|
417
|
-
defaultValue,
|
|
418
|
-
error,
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
return <fieldset ref={ref}>{/* ... */}</fieldset>;
|
|
422
|
-
}
|
|
423
277
|
```
|
|
424
278
|
|
|
425
|
-
<details>
|
|
426
|
-
<summary>What can I do with `controls`?</summary>
|
|
427
|
-
|
|
428
|
-
```tsx
|
|
429
|
-
// To append a new row with optional defaultValue
|
|
430
|
-
<button {...controls.append({ defaultValue })}>Append</button>;
|
|
431
|
-
|
|
432
|
-
// To prepend a new row with optional defaultValue
|
|
433
|
-
<button {...controls.prepend({ defaultValue })}>Prepend</button>;
|
|
434
|
-
|
|
435
|
-
// To remove a row by index
|
|
436
|
-
<button {...controls.remove({ index })}>Remove</button>;
|
|
437
|
-
|
|
438
|
-
// To replace a row with another defaultValue
|
|
439
|
-
<button {...controls.replace({ index, defaultValue })}>Replace</button>;
|
|
440
|
-
|
|
441
|
-
// To reorder a particular row to an another index
|
|
442
|
-
<button {...controls.reorder({ from, to })}>Reorder</button>;
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
</details>
|
|
446
|
-
|
|
447
279
|
---
|
|
448
280
|
|
|
449
281
|
### useControlledInput
|
|
450
282
|
|
|
451
|
-
It returns the properties required to configure a shadow input for validation. This is
|
|
283
|
+
It returns the properties required to configure a shadow input for validation and helper to integrate it. This is particularly useful when [integrating custom input components](/docs/integrations.md#custom-input-component) like dropdown and datepicker.
|
|
452
284
|
|
|
453
285
|
```tsx
|
|
454
|
-
import {
|
|
286
|
+
import { useForm, useControlledInput } from '@conform-to/react';
|
|
455
287
|
import { Select, MenuItem } from '@mui/material';
|
|
456
288
|
import { useRef } from 'react';
|
|
457
289
|
|
|
458
290
|
function MuiForm() {
|
|
459
|
-
const
|
|
460
|
-
const { category } = useFieldset(schema);
|
|
291
|
+
const [form, { category }] = useForm();
|
|
461
292
|
const [inputProps, control] = useControlledInput(category.config);
|
|
462
293
|
|
|
463
294
|
return (
|
|
464
|
-
<
|
|
295
|
+
<form {...form.props}>
|
|
465
296
|
{/* Render a shadow input somewhere */}
|
|
466
297
|
<input {...inputProps} />
|
|
467
298
|
|
|
468
299
|
{/* MUI Select is a controlled component */}
|
|
469
|
-
<
|
|
300
|
+
<TextField
|
|
470
301
|
label="Category"
|
|
471
302
|
inputRef={control.ref}
|
|
472
303
|
value={control.value}
|
|
473
304
|
onChange={control.onChange}
|
|
474
305
|
onBlur={control.onBlur}
|
|
475
306
|
inputProps={{
|
|
476
|
-
onInvalid: control.onInvalid
|
|
307
|
+
onInvalid: control.onInvalid,
|
|
477
308
|
}}
|
|
309
|
+
select
|
|
478
310
|
>
|
|
479
311
|
<MenuItem value="">Please select</MenuItem>
|
|
480
312
|
<MenuItem value="a">Category A</MenuItem>
|
|
481
313
|
<MenuItem value="b">Category B</MenuItem>
|
|
482
314
|
<MenuItem value="c">Category C</MenuItem>
|
|
483
315
|
</TextField>
|
|
484
|
-
</
|
|
316
|
+
</form>
|
|
485
317
|
);
|
|
486
318
|
}
|
|
487
319
|
```
|
|
@@ -490,55 +322,40 @@ function MuiForm() {
|
|
|
490
322
|
|
|
491
323
|
### conform
|
|
492
324
|
|
|
493
|
-
It provides several helpers to
|
|
494
|
-
|
|
495
|
-
```tsx
|
|
496
|
-
import { useFieldset, conform } from '@conform-to/react';
|
|
497
|
-
import { useRef } from 'react';
|
|
498
|
-
|
|
499
|
-
function RandomForm() {
|
|
500
|
-
const ref = useRef();
|
|
501
|
-
const { category } = useFieldset(ref);
|
|
325
|
+
It provides several helpers to remove the boilerplate when configuring a form control.
|
|
502
326
|
|
|
503
|
-
|
|
504
|
-
<fieldset ref={ref}>
|
|
505
|
-
<input {...conform.input(category.config, { type: 'text' })} />
|
|
506
|
-
<textarea {...conform.textarea(category.config)} />
|
|
507
|
-
<select {...conform.select(category.config)}>{/* ... */}</select>
|
|
508
|
-
</fieldset>
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
```
|
|
327
|
+
You are recommended to create a wrapper on top if you need to integrate with custom input component. As the helper derives attributes for [accessibility](/docs/accessibility.md#configuration) concerns and helps [focus management](/docs/focus-management.md#focusing-before-javascript-is-loaded).
|
|
512
328
|
|
|
513
|
-
|
|
329
|
+
Before:
|
|
514
330
|
|
|
515
331
|
```tsx
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
332
|
+
import { useForm } from '@conform-to/react';
|
|
333
|
+
|
|
334
|
+
function Example() {
|
|
335
|
+
const [form, { title, description, category }] = useForm();
|
|
519
336
|
|
|
520
337
|
return (
|
|
521
|
-
<
|
|
338
|
+
<form {...form.props}>
|
|
522
339
|
<input
|
|
523
340
|
type="text"
|
|
524
|
-
name={
|
|
525
|
-
form={
|
|
526
|
-
defaultValue={
|
|
527
|
-
requried={
|
|
528
|
-
minLength={
|
|
529
|
-
maxLength={
|
|
530
|
-
min={
|
|
531
|
-
max={
|
|
532
|
-
multiple={
|
|
533
|
-
pattern={
|
|
534
|
-
|
|
341
|
+
name={title.config.name}
|
|
342
|
+
form={title.config.form}
|
|
343
|
+
defaultValue={title.config.defaultValue}
|
|
344
|
+
requried={title.config.required}
|
|
345
|
+
minLength={title.config.minLength}
|
|
346
|
+
maxLength={title.config.maxLength}
|
|
347
|
+
min={title.config.min}
|
|
348
|
+
max={title.config.max}
|
|
349
|
+
multiple={title.config.multiple}
|
|
350
|
+
pattern={title.config.pattern}
|
|
351
|
+
/>
|
|
535
352
|
<textarea
|
|
536
|
-
name={
|
|
537
|
-
form={
|
|
538
|
-
defaultValue={
|
|
539
|
-
requried={
|
|
540
|
-
minLength={
|
|
541
|
-
maxLength={
|
|
353
|
+
name={description.config.name}
|
|
354
|
+
form={description.config.form}
|
|
355
|
+
defaultValue={description.config.defaultValue}
|
|
356
|
+
requried={description.config.required}
|
|
357
|
+
minLength={description.config.minLength}
|
|
358
|
+
maxLength={description.config.maxLength}
|
|
542
359
|
/>
|
|
543
360
|
<select
|
|
544
361
|
name={category.config.name}
|
|
@@ -549,7 +366,118 @@ function RandomForm() {
|
|
|
549
366
|
>
|
|
550
367
|
{/* ... */}
|
|
551
368
|
</select>
|
|
552
|
-
</
|
|
369
|
+
</form>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
After:
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
import { useForm, conform } from '@conform-to/react';
|
|
378
|
+
|
|
379
|
+
function Example() {
|
|
380
|
+
const [form, { title, description, category }] = useForm();
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
<form {...form.props}>
|
|
384
|
+
<input {...conform.input(title.config, { type: 'text' })} />
|
|
385
|
+
<textarea {...conform.textarea(description.config)} />
|
|
386
|
+
<select {...conform.select(category.config)}>{/* ... */}</select>
|
|
387
|
+
</form>
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
### list
|
|
395
|
+
|
|
396
|
+
It provides serveral helpers to configure a command button for [modifying a list](/docs/commands.md#modifying-a-list).
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
import { list } from '@conform-to/react';
|
|
400
|
+
|
|
401
|
+
function Example() {
|
|
402
|
+
return (
|
|
403
|
+
<form>
|
|
404
|
+
{/* To append a new row with optional defaultValue */}
|
|
405
|
+
<button {...list.append('name', { defaultValue })}>Append</button>
|
|
406
|
+
|
|
407
|
+
{/* To prepend a new row with optional defaultValue */}
|
|
408
|
+
<button {...list.prepend('name', { defaultValue })}>Prepend</button>
|
|
409
|
+
|
|
410
|
+
{/* To remove a row by index */}
|
|
411
|
+
<button {...list.remove('name', { index })}>Remove</button>
|
|
412
|
+
|
|
413
|
+
{/* To replace a row with another defaultValue */}
|
|
414
|
+
<button {...list.replace('name', { index, defaultValue })}>
|
|
415
|
+
Replace
|
|
416
|
+
</button>
|
|
417
|
+
|
|
418
|
+
{/* To reorder a particular row to an another index */}
|
|
419
|
+
<button {...list.reorder('name', { from, to })}>Reorder</button>
|
|
420
|
+
</form>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
### validate
|
|
428
|
+
|
|
429
|
+
It returns the properties required to configure a command button for [validation](/docs/commands.md#validation).
|
|
430
|
+
|
|
431
|
+
```tsx
|
|
432
|
+
import { validate } from '@conform-to/react';
|
|
433
|
+
|
|
434
|
+
function Example() {
|
|
435
|
+
return (
|
|
436
|
+
<form>
|
|
437
|
+
{/* To validate a single field by name */}
|
|
438
|
+
<button {...validate('email')}>Validate email</button>
|
|
439
|
+
|
|
440
|
+
{/* To validate the whole form */}
|
|
441
|
+
<button {...validate()}>Validate</button>
|
|
442
|
+
</form>
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
### requestCommand
|
|
450
|
+
|
|
451
|
+
It lets you [trigger a command](/docs/commands.md#triggering-a-command) without requiring users to click on a button. It supports both [list](#list) and [validate](#validate) command.
|
|
452
|
+
|
|
453
|
+
```tsx
|
|
454
|
+
import {
|
|
455
|
+
useForm,
|
|
456
|
+
useFieldList,
|
|
457
|
+
conform,
|
|
458
|
+
list,
|
|
459
|
+
requestCommand,
|
|
460
|
+
} from '@conform-to/react';
|
|
461
|
+
import DragAndDrop from 'awesome-dnd-example';
|
|
462
|
+
|
|
463
|
+
export default function Todos() {
|
|
464
|
+
const [form, { tasks }] = useForm();
|
|
465
|
+
const taskList = useFieldList(form.ref, tasks.config);
|
|
466
|
+
|
|
467
|
+
const handleDrop = (from, to) =>
|
|
468
|
+
requestCommand(form.ref.current, list.reorder({ from, to }));
|
|
469
|
+
|
|
470
|
+
return (
|
|
471
|
+
<form {...form.props}>
|
|
472
|
+
<DragAndDrop onDrop={handleDrop}>
|
|
473
|
+
{taskList.map((task, index) => (
|
|
474
|
+
<div key={task.key}>
|
|
475
|
+
<input {...conform.input(task.config)} />
|
|
476
|
+
</div>
|
|
477
|
+
))}
|
|
478
|
+
</DragAndDrop>
|
|
479
|
+
<button>Save</button>
|
|
480
|
+
</form>
|
|
553
481
|
);
|
|
554
482
|
}
|
|
555
483
|
```
|
|
@@ -558,13 +486,13 @@ function RandomForm() {
|
|
|
558
486
|
|
|
559
487
|
### getFormElements
|
|
560
488
|
|
|
561
|
-
It returns all _input_ / _select_ / _textarea_ or _button_ in the forms. Useful when looping through the form elements to validate each field.
|
|
489
|
+
It returns all _input_ / _select_ / _textarea_ or _button_ in the forms. Useful when looping through the form elements to validate each field manually.
|
|
562
490
|
|
|
563
491
|
```tsx
|
|
564
492
|
import { useForm, parse, getFormElements } from '@conform-to/react';
|
|
565
493
|
|
|
566
494
|
export default function LoginForm() {
|
|
567
|
-
const form = useForm({
|
|
495
|
+
const [form] = useForm({
|
|
568
496
|
onValidate({ form, formData }) {
|
|
569
497
|
const submission = parse(formData);
|
|
570
498
|
|