@conform-to/react 0.5.0-pre.0 → 0.5.1

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 CHANGED
@@ -9,8 +9,12 @@
9
9
  - [useForm](#useform)
10
10
  - [useFieldset](#usefieldset)
11
11
  - [useFieldList](#usefieldlist)
12
+ - [useInputEvent](#useinputevent)
12
13
  - [useControlledInput](#usecontrolledinput)
13
14
  - [conform](#conform)
15
+ - [list](#list)
16
+ - [validate](#validate)
17
+ - [requestCommand](#requestcommand)
14
18
  - [getFormElements](#getformelements)
15
19
  - [hasError](#haserror)
16
20
  - [parse](#parse)
@@ -20,19 +24,25 @@
20
24
 
21
25
  ### useForm
22
26
 
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 it is submitted. This checks the validity of all the fields in it and reports if there are errors through the bubbles.
27
+ 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
28
 
25
- This hook enhances the form validation behaviour in 3 parts:
29
+ This hook enhances the form validation behaviour by:
26
30
 
27
- 1. It enhances form validation with custom rules by subscribing to different DOM events and reporting the errors only when it is configured to do so.
28
- 2. It unifies client and server validation in one place.
29
- 3. It exposes the state of each field in the form of [data attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*), such as `data-conform-touched`, allowing flexible styling across your form without the need to manipulate the class names.
31
+ - Enabling customizing form validation behaviour.
32
+ - Capturing the error message and removes the error bubbles.
33
+ - Preparing all properties required to configure the dom elements.
30
34
 
31
35
  ```tsx
32
36
  import { useForm } from '@conform-to/react';
33
37
 
34
38
  function LoginForm() {
35
- const form = useForm({
39
+ const [form, { email, password }] = useForm({
40
+ /**
41
+ * If the form id is provided, Id for label,
42
+ * input and error elements will be derived.
43
+ */
44
+ id: undefined,
45
+
36
46
  /**
37
47
  * Validation mode.
38
48
  * Support "client-only" or "server-validation".
@@ -59,6 +69,11 @@ function LoginForm() {
59
69
  */
60
70
  state: undefined;
61
71
 
72
+ /**
73
+ * An object describing the constraint of each field
74
+ */
75
+ constraint: undefined;
76
+
62
77
  /**
63
78
  * Enable native validation before hydation.
64
79
  *
@@ -77,14 +92,14 @@ function LoginForm() {
77
92
  * A function to be called when the form should be (re)validated.
78
93
  * Only sync validation is supported
79
94
  */
80
- onValidate({ form, formData, submission }) {
95
+ onValidate({ form, formData }) {
81
96
  // ...
82
97
  },
83
98
 
84
99
  /**
85
100
  * The submit event handler of the form.
86
101
  */
87
- onSubmit(event, { form, formData, submission }) {
102
+ onSubmit(event, { formData, submission }) {
88
103
  // ...
89
104
  },
90
105
  });
@@ -96,15 +111,16 @@ function LoginForm() {
96
111
  <details>
97
112
  <summary>What is `form.props`?</summary>
98
113
 
99
- It is a group of properties properties required to hook into form events. They can also be set explicitly as shown below:
114
+ It is a group of properties required to hook into form events. They can also be set explicitly as shown below:
100
115
 
101
116
  ```tsx
102
117
  function RandomForm() {
103
- const form = useForm();
118
+ const [form] = useForm();
104
119
 
105
120
  return (
106
121
  <form
107
122
  ref={form.props.ref}
123
+ id={form.props.id}
108
124
  onSubmit={form.props.onSubmit}
109
125
  noValidate={form.props.noValidate}
110
126
  >
@@ -126,7 +142,7 @@ import { useFrom } from '@conform-to/react';
126
142
  import { Form } from '@remix-run/react';
127
143
 
128
144
  function LoginForm() {
129
- const form = useForm();
145
+ const [form] = useForm();
130
146
 
131
147
  return (
132
148
  <Form method="post" action="/login" {...form.props}>
@@ -138,157 +154,57 @@ function LoginForm() {
138
154
 
139
155
  </details>
140
156
 
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
157
  ---
172
158
 
173
159
  ### useFieldset
174
160
 
175
- This hook can be used to monitor the state of each field and help configuration. It lets you:
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.
161
+ 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
162
 
180
163
  ```tsx
181
- import { useForm, useFieldset } from '@conform-to/react';
164
+ import { useForm, useFieldset, conform } from '@conform-to/react';
182
165
 
183
- /**
184
- * Consider the schema as follow:
185
- */
186
- type Book = {
187
- name: string;
188
- isbn: string;
189
- };
166
+ interface Address {
167
+ street: string;
168
+ zipcode: string;
169
+ city: string;
170
+ country: string;
171
+ }
190
172
 
191
- function BookFieldset() {
192
- const formProps = useForm();
193
- const { name, isbn } = useFieldset<Book>(
194
- /**
195
- * A ref object of the form element or fieldset element
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
- },
173
+ function Example() {
174
+ const [form, { address }] = useForm<{ address: Address }>();
175
+ const { city, zipcode, street, country } = useFieldset(
176
+ form.ref,
177
+ address.config,
234
178
  );
235
179
 
236
- /**
237
- * Latest error of the field
238
- * This would be 'Invalid ISBN' initially as specified
239
- * in the initialError config
240
- */
241
- console.log(isbn.error);
242
-
243
- /**
244
- * This would be `book.isbn` instead of `isbn`
245
- * if the `name` option is provided
246
- */
247
- console.log(isbn.config.name);
248
-
249
- /**
250
- * This would be `0340013818` if specified
251
- * on the `initalValue` option
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>;
180
+ return (
181
+ <form {...form.props}>
182
+ <fieldset>
183
+ <legned>Address</legend>
184
+ <input {...conform.input(street.config)} />
185
+ <div>{street.error}</div>
186
+ <input {...conform.input(zipcode.config)} />
187
+ <div>{zipcode.error}</div>
188
+ <input {...conform.input(city.config)} />
189
+ <div>{city.error}</div>
190
+ <input {...conform.input(country.config)} />
191
+ <div>{country.error}</div>
192
+ </fieldset>
193
+ <button>Submit</button>
194
+ </form>
195
+ );
280
196
  }
281
197
  ```
282
198
 
283
199
  If you don't have direct access to the form ref, you can also pass a fieldset ref.
284
200
 
285
201
  ```tsx
286
- import { useFieldset } from '@conform-to/react';
202
+ import { type FieldConfig, useFieldset } from '@conform-to/react';
287
203
  import { useRef } from 'react';
288
204
 
289
- function Fieldset() {
290
- const ref = useRef();
291
- const fieldset = useFieldset(ref);
205
+ function Fieldset(config: FieldConfig<Address>) {
206
+ const ref = useRef<HTMLFieldsetElement>(null);
207
+ const { city, zipcode, street, country } = useFieldset(ref, config);
292
208
 
293
209
  return <fieldset ref={ref}>{/* ... */}</fieldset>;
294
210
  }
@@ -297,7 +213,7 @@ function Fieldset() {
297
213
  <details>
298
214
  <summary>Why does `useFieldset` require a ref object of the form or fieldset?</summary>
299
215
 
300
- Unlike most of the form validation library out there, **conform** use the DOM as its context provider. As the dom 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) of these elements. The ref object allows us restricting the scope to elements associated to the same form only.
216
+ **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
217
 
302
218
  ```tsx
303
219
  function ExampleForm() {
@@ -324,23 +240,21 @@ function ExampleForm() {
324
240
 
325
241
  ### useFieldList
326
242
 
327
- It returns a list of key, config and error, with helpers to configure [list command](/docs/submission.md#list-command) button.
243
+ 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
244
 
329
245
  ```tsx
330
- import { useFieldset, useFieldList } from '@conform-to/react';
331
- import { useRef } from 'react';
246
+ import { useForm, useFieldList, list } from '@conform-to/react';
332
247
 
333
248
  /**
334
249
  * Consider the schema as follow:
335
250
  */
336
251
  type Schema = {
337
- list: string[];
252
+ items: string[];
338
253
  };
339
254
 
340
- function CollectionFieldset() {
341
- const ref = useRef<HTMLFieldsetElement>(null);
342
- const fieldset = useFieldset<Collection>(ref);
343
- const [list, command] = useFieldList(ref, fieldset.list.config);
255
+ function Example() {
256
+ const [form, { items }] = useForm<Schema>();
257
+ const list = useFieldList(form.ref, items.config);
344
258
 
345
259
  return (
346
260
  <fieldset ref={ref}>
@@ -349,137 +263,110 @@ function CollectionFieldset() {
349
263
  {/* Setup an input per item */}
350
264
  <input {...conform.input(item.config)} />
351
265
 
352
- {/* Error of each book */}
266
+ {/* Error of each item */}
353
267
  <span>{item.error}</span>
354
268
 
355
- {/* To setup a delete button */}
356
- <button {...command.remove({ index })}>Delete</button>
269
+ {/* Setup a delete button (Note: It is `items` not `item`) */}
270
+ <button {...list.remove(items.config.name, { index })}>Delete</button>
357
271
  </div>
358
272
  ))}
359
273
 
360
- {/* To setup a button that can append a new row with optional default value */}
361
- <button {...command.append({ defaultValue: '' })}>add</button>
274
+ {/* Setup a button that can append a new row with optional default value */}
275
+ <button {...list.append(items.config.name, { defaultValue: '' })}>
276
+ add
277
+ </button>
362
278
  </fieldset>
363
279
  );
364
280
  }
365
281
  ```
366
282
 
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
- };
283
+ ---
384
284
 
385
- type Item = {
386
- title: string;
387
- description: string;
388
- };
285
+ ### useInputEvent
389
286
 
390
- function CollectionFieldset() {
391
- const ref = useRef<HTMLFieldsetElement>(null);
392
- const fieldset = useFieldset<Collection>(ref);
393
- const [list, command] = useFieldList(ref, fieldset.list.config);
287
+ It returns a ref object and a set of helpers that dispatch corresponding dom event.
394
288
 
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} />
401
- </div>
402
- ))}
403
- </fieldset>
404
- );
405
- }
289
+ ```tsx
290
+ import { useForm, useInputEvent } from '@conform-to/react';
291
+ import { Select, MenuItem } from '@mui/material';
292
+ import { useState, useRef } from 'react';
406
293
 
407
- function ItemFieldset(config: FieldConfig<Item>) {
408
- const ref = useRef<HTMLFieldsetElement>(null);
409
- const { title, description } = useFieldset(ref, config);
294
+ function MuiForm() {
295
+ const [form, { category }] = useForm();
296
+ const [value, setValue] = useState(category.config.defaultValue ?? '');
297
+ const [ref, control] = useInputEvent({
298
+ onReset: () => setValue(category.config.defaultValue ?? ''),
299
+ });
300
+ const inputRef = useRef<HTMLInputElement>(null);
410
301
 
411
302
  return (
412
- <fieldset ref={ref}>
413
- <input {...conform.input(title.config)} />
414
- <span>{title.error}</span>
303
+ <form {...form.props}>
304
+ {/* Render a shadow input somewhere */}
305
+ <input
306
+ ref={ref}
307
+ {...conform.input(category.config, { hidden: true })}
308
+ onChange={(e) => setValue(e.target.value)}
309
+ onFocus={() => inputRef.current?.focus()}
310
+ />
415
311
 
416
- <input {...conform.input(description.config)} />
417
- <span>{description.error}</span>
418
- </fieldset>
312
+ {/* MUI Select is a controlled component */}
313
+ <TextField
314
+ label="Category"
315
+ inputRef={inputRef}
316
+ value={value}
317
+ onChange={control.change}
318
+ onBlur={control.blur}
319
+ select
320
+ >
321
+ <MenuItem value="">Please select</MenuItem>
322
+ <MenuItem value="a">Category A</MenuItem>
323
+ <MenuItem value="b">Category B</MenuItem>
324
+ <MenuItem value="c">Category C</MenuItem>
325
+ </TextField>
326
+ </form>
419
327
  );
420
328
  }
421
329
  ```
422
330
 
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
331
  ---
446
332
 
447
333
  ### useControlledInput
448
334
 
449
- It returns the properties required to configure a shadow input for validation. This is particular useful when integrating dropdown and datepicker whichs introduces custom input mode.
335
+ > This API is deprecated and replaced with the [useInputEvent](#useinputevent) hook.
336
+
337
+ 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
338
 
451
339
  ```tsx
452
- import { useFieldset, useControlledInput } from '@conform-to/react';
340
+ import { useForm, useControlledInput } from '@conform-to/react';
453
341
  import { Select, MenuItem } from '@mui/material';
454
- import { useRef } from 'react';
455
342
 
456
343
  function MuiForm() {
457
- const ref = useRef();
458
- const { category } = useFieldset(schema);
344
+ const [form, { category }] = useForm();
459
345
  const [inputProps, control] = useControlledInput(category.config);
460
346
 
461
347
  return (
462
- <fieldset ref={ref}>
348
+ <form {...form.props}>
463
349
  {/* Render a shadow input somewhere */}
464
350
  <input {...inputProps} />
465
351
 
466
352
  {/* MUI Select is a controlled component */}
467
- <Select
353
+ <TextField
468
354
  label="Category"
469
355
  inputRef={control.ref}
470
356
  value={control.value}
471
357
  onChange={control.onChange}
472
358
  onBlur={control.onBlur}
473
359
  inputProps={{
474
- onInvalid: control.onInvalid
360
+ onInvalid: control.onInvalid,
475
361
  }}
362
+ select
476
363
  >
477
364
  <MenuItem value="">Please select</MenuItem>
478
365
  <MenuItem value="a">Category A</MenuItem>
479
366
  <MenuItem value="b">Category B</MenuItem>
480
367
  <MenuItem value="c">Category C</MenuItem>
481
368
  </TextField>
482
- </fieldset>
369
+ </form>
483
370
  );
484
371
  }
485
372
  ```
@@ -488,55 +375,40 @@ function MuiForm() {
488
375
 
489
376
  ### conform
490
377
 
491
- It provides several helpers to configure a native input field quickly:
378
+ It provides several helpers to remove the boilerplate when configuring a form control.
492
379
 
493
- ```tsx
494
- import { useFieldset, conform } from '@conform-to/react';
495
- import { useRef } from 'react';
380
+ 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).
496
381
 
497
- function RandomForm() {
498
- const ref = useRef();
499
- const { category } = useFieldset(ref);
500
-
501
- return (
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
- ```
510
-
511
- This is equivalent to:
382
+ Before:
512
383
 
513
384
  ```tsx
514
- function RandomForm() {
515
- const ref = useRef();
516
- const { category } = useFieldset(ref);
385
+ import { useForm } from '@conform-to/react';
386
+
387
+ function Example() {
388
+ const [form, { title, description, category }] = useForm();
517
389
 
518
390
  return (
519
- <fieldset ref={ref}>
391
+ <form {...form.props}>
520
392
  <input
521
393
  type="text"
522
- name={category.config.name}
523
- form={category.config.form}
524
- defaultValue={category.config.defaultValue}
525
- requried={category.config.required}
526
- minLength={category.config.minLength}
527
- maxLength={category.config.maxLength}
528
- min={category.config.min}
529
- max={category.config.max}
530
- multiple={category.config.multiple}
531
- pattern={category.config.pattern}
532
- >
394
+ name={title.config.name}
395
+ form={title.config.form}
396
+ defaultValue={title.config.defaultValue}
397
+ requried={title.config.required}
398
+ minLength={title.config.minLength}
399
+ maxLength={title.config.maxLength}
400
+ min={title.config.min}
401
+ max={title.config.max}
402
+ multiple={title.config.multiple}
403
+ pattern={title.config.pattern}
404
+ />
533
405
  <textarea
534
- name={category.config.name}
535
- form={category.config.form}
536
- defaultValue={category.config.defaultValue}
537
- requried={category.config.required}
538
- minLength={category.config.minLength}
539
- maxLength={category.config.maxLength}
406
+ name={description.config.name}
407
+ form={description.config.form}
408
+ defaultValue={description.config.defaultValue}
409
+ requried={description.config.required}
410
+ minLength={description.config.minLength}
411
+ maxLength={description.config.maxLength}
540
412
  />
541
413
  <select
542
414
  name={category.config.name}
@@ -547,7 +419,118 @@ function RandomForm() {
547
419
  >
548
420
  {/* ... */}
549
421
  </select>
550
- </fieldset>
422
+ </form>
423
+ );
424
+ }
425
+ ```
426
+
427
+ After:
428
+
429
+ ```tsx
430
+ import { useForm, conform } from '@conform-to/react';
431
+
432
+ function Example() {
433
+ const [form, { title, description, category }] = useForm();
434
+
435
+ return (
436
+ <form {...form.props}>
437
+ <input {...conform.input(title.config, { type: 'text' })} />
438
+ <textarea {...conform.textarea(description.config)} />
439
+ <select {...conform.select(category.config)}>{/* ... */}</select>
440
+ </form>
441
+ );
442
+ }
443
+ ```
444
+
445
+ ---
446
+
447
+ ### list
448
+
449
+ It provides serveral helpers to configure a command button for [modifying a list](/docs/commands.md#modifying-a-list).
450
+
451
+ ```tsx
452
+ import { list } from '@conform-to/react';
453
+
454
+ function Example() {
455
+ return (
456
+ <form>
457
+ {/* To append a new row with optional defaultValue */}
458
+ <button {...list.append('name', { defaultValue })}>Append</button>
459
+
460
+ {/* To prepend a new row with optional defaultValue */}
461
+ <button {...list.prepend('name', { defaultValue })}>Prepend</button>
462
+
463
+ {/* To remove a row by index */}
464
+ <button {...list.remove('name', { index })}>Remove</button>
465
+
466
+ {/* To replace a row with another defaultValue */}
467
+ <button {...list.replace('name', { index, defaultValue })}>
468
+ Replace
469
+ </button>
470
+
471
+ {/* To reorder a particular row to an another index */}
472
+ <button {...list.reorder('name', { from, to })}>Reorder</button>
473
+ </form>
474
+ );
475
+ }
476
+ ```
477
+
478
+ ---
479
+
480
+ ### validate
481
+
482
+ It returns the properties required to configure a command button for [validation](/docs/commands.md#validation).
483
+
484
+ ```tsx
485
+ import { validate } from '@conform-to/react';
486
+
487
+ function Example() {
488
+ return (
489
+ <form>
490
+ {/* To validate a single field by name */}
491
+ <button {...validate('email')}>Validate email</button>
492
+
493
+ {/* To validate the whole form */}
494
+ <button {...validate()}>Validate</button>
495
+ </form>
496
+ );
497
+ }
498
+ ```
499
+
500
+ ---
501
+
502
+ ### requestCommand
503
+
504
+ 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.
505
+
506
+ ```tsx
507
+ import {
508
+ useForm,
509
+ useFieldList,
510
+ conform,
511
+ list,
512
+ requestCommand,
513
+ } from '@conform-to/react';
514
+ import DragAndDrop from 'awesome-dnd-example';
515
+
516
+ export default function Todos() {
517
+ const [form, { tasks }] = useForm();
518
+ const taskList = useFieldList(form.ref, tasks.config);
519
+
520
+ const handleDrop = (from, to) =>
521
+ requestCommand(form.ref.current, list.reorder({ from, to }));
522
+
523
+ return (
524
+ <form {...form.props}>
525
+ <DragAndDrop onDrop={handleDrop}>
526
+ {taskList.map((task, index) => (
527
+ <div key={task.key}>
528
+ <input {...conform.input(task.config)} />
529
+ </div>
530
+ ))}
531
+ </DragAndDrop>
532
+ <button>Save</button>
533
+ </form>
551
534
  );
552
535
  }
553
536
  ```
@@ -556,13 +539,13 @@ function RandomForm() {
556
539
 
557
540
  ### getFormElements
558
541
 
559
- It returns all _input_ / _select_ / _textarea_ or _button_ in the forms. Useful when looping through the form elements to validate each field.
542
+ It returns all _input_ / _select_ / _textarea_ or _button_ in the forms. Useful when looping through the form elements to validate each field manually.
560
543
 
561
544
  ```tsx
562
545
  import { useForm, parse, getFormElements } from '@conform-to/react';
563
546
 
564
547
  export default function LoginForm() {
565
- const form = useForm({
548
+ const [form] = useForm({
566
549
  onValidate({ form, formData }) {
567
550
  const submission = parse(formData);
568
551