@conform-to/react 0.5.0-pre.0 → 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 +217 -287
- package/helpers.d.ts +3 -0
- package/helpers.js +15 -5
- package/hooks.d.ts +22 -29
- package/hooks.js +55 -67
- package/index.d.ts +1 -1
- package/index.js +16 -0
- package/module/helpers.js +15 -5
- package/module/hooks.js +56 -68
- 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,23 +236,21 @@ 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
247
|
type Schema = {
|
|
337
|
-
|
|
248
|
+
items: string[];
|
|
338
249
|
};
|
|
339
250
|
|
|
340
|
-
function
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
const [list, command] = useFieldList(ref, fieldset.list.config);
|
|
251
|
+
function Example() {
|
|
252
|
+
const [form, { items }] = useForm<Schema>();
|
|
253
|
+
const list = useFieldList(form.ref, items.config);
|
|
344
254
|
|
|
345
255
|
return (
|
|
346
256
|
<fieldset ref={ref}>
|
|
@@ -349,137 +259,61 @@ function CollectionFieldset() {
|
|
|
349
259
|
{/* Setup an input per item */}
|
|
350
260
|
<input {...conform.input(item.config)} />
|
|
351
261
|
|
|
352
|
-
{/* Error of each
|
|
262
|
+
{/* Error of each item */}
|
|
353
263
|
<span>{item.error}</span>
|
|
354
264
|
|
|
355
|
-
{/*
|
|
356
|
-
<button {...
|
|
357
|
-
</div>
|
|
358
|
-
))}
|
|
359
|
-
|
|
360
|
-
{/* To setup a button that can append a new row with optional default value */}
|
|
361
|
-
<button {...command.append({ defaultValue: '' })}>add</button>
|
|
362
|
-
</fieldset>
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
This hook can also be used in combination with `useFieldset` for nested list:
|
|
368
|
-
|
|
369
|
-
```tsx
|
|
370
|
-
import {
|
|
371
|
-
type FieldConfig,
|
|
372
|
-
useForm,
|
|
373
|
-
useFieldset,
|
|
374
|
-
useFieldList,
|
|
375
|
-
} from '@conform-to/react';
|
|
376
|
-
import { useRef } from 'react';
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Consider the schema as follow:
|
|
380
|
-
*/
|
|
381
|
-
type Schema = {
|
|
382
|
-
list: Array<Item>;
|
|
383
|
-
};
|
|
384
|
-
|
|
385
|
-
type Item = {
|
|
386
|
-
title: string;
|
|
387
|
-
description: string;
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
function CollectionFieldset() {
|
|
391
|
-
const ref = useRef<HTMLFieldsetElement>(null);
|
|
392
|
-
const fieldset = useFieldset<Collection>(ref);
|
|
393
|
-
const [list, command] = useFieldList(ref, fieldset.list.config);
|
|
394
|
-
|
|
395
|
-
return (
|
|
396
|
-
<fieldset ref={ref}>
|
|
397
|
-
{list.map((item, index) => (
|
|
398
|
-
<div key={item.key}>
|
|
399
|
-
{/* Pass the item config to another fieldset*/}
|
|
400
|
-
<ItemFieldset {...item.config} />
|
|
265
|
+
{/* Setup a delete button (Note: It is `items` not `item`) */}
|
|
266
|
+
<button {...list.remove(items.config.name, { index })}>Delete</button>
|
|
401
267
|
</div>
|
|
402
268
|
))}
|
|
403
|
-
</fieldset>
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function ItemFieldset(config: FieldConfig<Item>) {
|
|
408
|
-
const ref = useRef<HTMLFieldsetElement>(null);
|
|
409
|
-
const { title, description } = useFieldset(ref, config);
|
|
410
269
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
<input {...conform.input(description.config)} />
|
|
417
|
-
<span>{description.error}</span>
|
|
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>
|
|
418
274
|
</fieldset>
|
|
419
275
|
);
|
|
420
276
|
}
|
|
421
277
|
```
|
|
422
278
|
|
|
423
|
-
<details>
|
|
424
|
-
<summary>What can I do with `command`?</summary>
|
|
425
|
-
|
|
426
|
-
```tsx
|
|
427
|
-
// To append a new row with optional defaultValue
|
|
428
|
-
<button {...command.append({ defaultValue })}>Append</button>;
|
|
429
|
-
|
|
430
|
-
// To prepend a new row with optional defaultValue
|
|
431
|
-
<button {...command.prepend({ defaultValue })}>Prepend</button>;
|
|
432
|
-
|
|
433
|
-
// To remove a row by index
|
|
434
|
-
<button {...command.remove({ index })}>Remove</button>;
|
|
435
|
-
|
|
436
|
-
// To replace a row with another defaultValue
|
|
437
|
-
<button {...command.replace({ index, defaultValue })}>Replace</button>;
|
|
438
|
-
|
|
439
|
-
// To reorder a particular row to an another index
|
|
440
|
-
<button {...command.reorder({ from, to })}>Reorder</button>;
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
</details>
|
|
444
|
-
|
|
445
279
|
---
|
|
446
280
|
|
|
447
281
|
### useControlledInput
|
|
448
282
|
|
|
449
|
-
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.
|
|
450
284
|
|
|
451
285
|
```tsx
|
|
452
|
-
import {
|
|
286
|
+
import { useForm, useControlledInput } from '@conform-to/react';
|
|
453
287
|
import { Select, MenuItem } from '@mui/material';
|
|
454
288
|
import { useRef } from 'react';
|
|
455
289
|
|
|
456
290
|
function MuiForm() {
|
|
457
|
-
const
|
|
458
|
-
const { category } = useFieldset(schema);
|
|
291
|
+
const [form, { category }] = useForm();
|
|
459
292
|
const [inputProps, control] = useControlledInput(category.config);
|
|
460
293
|
|
|
461
294
|
return (
|
|
462
|
-
<
|
|
295
|
+
<form {...form.props}>
|
|
463
296
|
{/* Render a shadow input somewhere */}
|
|
464
297
|
<input {...inputProps} />
|
|
465
298
|
|
|
466
299
|
{/* MUI Select is a controlled component */}
|
|
467
|
-
<
|
|
300
|
+
<TextField
|
|
468
301
|
label="Category"
|
|
469
302
|
inputRef={control.ref}
|
|
470
303
|
value={control.value}
|
|
471
304
|
onChange={control.onChange}
|
|
472
305
|
onBlur={control.onBlur}
|
|
473
306
|
inputProps={{
|
|
474
|
-
onInvalid: control.onInvalid
|
|
307
|
+
onInvalid: control.onInvalid,
|
|
475
308
|
}}
|
|
309
|
+
select
|
|
476
310
|
>
|
|
477
311
|
<MenuItem value="">Please select</MenuItem>
|
|
478
312
|
<MenuItem value="a">Category A</MenuItem>
|
|
479
313
|
<MenuItem value="b">Category B</MenuItem>
|
|
480
314
|
<MenuItem value="c">Category C</MenuItem>
|
|
481
315
|
</TextField>
|
|
482
|
-
</
|
|
316
|
+
</form>
|
|
483
317
|
);
|
|
484
318
|
}
|
|
485
319
|
```
|
|
@@ -488,55 +322,40 @@ function MuiForm() {
|
|
|
488
322
|
|
|
489
323
|
### conform
|
|
490
324
|
|
|
491
|
-
It provides several helpers to
|
|
492
|
-
|
|
493
|
-
```tsx
|
|
494
|
-
import { useFieldset, conform } from '@conform-to/react';
|
|
495
|
-
import { useRef } from 'react';
|
|
496
|
-
|
|
497
|
-
function RandomForm() {
|
|
498
|
-
const ref = useRef();
|
|
499
|
-
const { category } = useFieldset(ref);
|
|
325
|
+
It provides several helpers to remove the boilerplate when configuring a form control.
|
|
500
326
|
|
|
501
|
-
|
|
502
|
-
<fieldset ref={ref}>
|
|
503
|
-
<input {...conform.input(category.config, { type: 'text' })} />
|
|
504
|
-
<textarea {...conform.textarea(category.config)} />
|
|
505
|
-
<select {...conform.select(category.config)}>{/* ... */}</select>
|
|
506
|
-
</fieldset>
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
```
|
|
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).
|
|
510
328
|
|
|
511
|
-
|
|
329
|
+
Before:
|
|
512
330
|
|
|
513
331
|
```tsx
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
332
|
+
import { useForm } from '@conform-to/react';
|
|
333
|
+
|
|
334
|
+
function Example() {
|
|
335
|
+
const [form, { title, description, category }] = useForm();
|
|
517
336
|
|
|
518
337
|
return (
|
|
519
|
-
<
|
|
338
|
+
<form {...form.props}>
|
|
520
339
|
<input
|
|
521
340
|
type="text"
|
|
522
|
-
name={
|
|
523
|
-
form={
|
|
524
|
-
defaultValue={
|
|
525
|
-
requried={
|
|
526
|
-
minLength={
|
|
527
|
-
maxLength={
|
|
528
|
-
min={
|
|
529
|
-
max={
|
|
530
|
-
multiple={
|
|
531
|
-
pattern={
|
|
532
|
-
|
|
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
|
+
/>
|
|
533
352
|
<textarea
|
|
534
|
-
name={
|
|
535
|
-
form={
|
|
536
|
-
defaultValue={
|
|
537
|
-
requried={
|
|
538
|
-
minLength={
|
|
539
|
-
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}
|
|
540
359
|
/>
|
|
541
360
|
<select
|
|
542
361
|
name={category.config.name}
|
|
@@ -547,7 +366,118 @@ function RandomForm() {
|
|
|
547
366
|
>
|
|
548
367
|
{/* ... */}
|
|
549
368
|
</select>
|
|
550
|
-
</
|
|
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>
|
|
551
481
|
);
|
|
552
482
|
}
|
|
553
483
|
```
|
|
@@ -556,13 +486,13 @@ function RandomForm() {
|
|
|
556
486
|
|
|
557
487
|
### getFormElements
|
|
558
488
|
|
|
559
|
-
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.
|
|
560
490
|
|
|
561
491
|
```tsx
|
|
562
492
|
import { useForm, parse, getFormElements } from '@conform-to/react';
|
|
563
493
|
|
|
564
494
|
export default function LoginForm() {
|
|
565
|
-
const form = useForm({
|
|
495
|
+
const [form] = useForm({
|
|
566
496
|
onValidate({ form, formData }) {
|
|
567
497
|
const submission = parse(formData);
|
|
568
498
|
|