@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 +260 -277
- package/helpers.d.ts +16 -7
- package/helpers.js +47 -7
- package/hooks.d.ts +48 -31
- package/hooks.js +235 -81
- package/index.d.ts +1 -1
- package/index.js +17 -0
- package/module/helpers.js +47 -7
- package/module/hooks.js +237 -84
- package/module/index.js +2 -2
- package/package.json +2 -2
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
|
|
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
|
|
29
|
+
This hook enhances the form validation behaviour by:
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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, {
|
|
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
|
|
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
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
};
|
|
166
|
+
interface Address {
|
|
167
|
+
street: string;
|
|
168
|
+
zipcode: string;
|
|
169
|
+
city: string;
|
|
170
|
+
country: string;
|
|
171
|
+
}
|
|
190
172
|
|
|
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
|
-
},
|
|
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
|
-
|
|
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>;
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
252
|
+
items: string[];
|
|
338
253
|
};
|
|
339
254
|
|
|
340
|
-
function
|
|
341
|
-
const
|
|
342
|
-
const
|
|
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
|
|
266
|
+
{/* Error of each item */}
|
|
353
267
|
<span>{item.error}</span>
|
|
354
268
|
|
|
355
|
-
{/*
|
|
356
|
-
<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
|
-
{/*
|
|
361
|
-
<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
|
-
|
|
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
|
-
|
|
386
|
-
title: string;
|
|
387
|
-
description: string;
|
|
388
|
-
};
|
|
285
|
+
### useInputEvent
|
|
389
286
|
|
|
390
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
|
408
|
-
const
|
|
409
|
-
const
|
|
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
|
-
<
|
|
413
|
-
|
|
414
|
-
<
|
|
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
|
-
|
|
417
|
-
<
|
|
418
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
458
|
-
const { category } = useFieldset(schema);
|
|
344
|
+
const [form, { category }] = useForm();
|
|
459
345
|
const [inputProps, control] = useControlledInput(category.config);
|
|
460
346
|
|
|
461
347
|
return (
|
|
462
|
-
<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
|
378
|
+
It provides several helpers to remove the boilerplate when configuring a form control.
|
|
492
379
|
|
|
493
|
-
|
|
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
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
385
|
+
import { useForm } from '@conform-to/react';
|
|
386
|
+
|
|
387
|
+
function Example() {
|
|
388
|
+
const [form, { title, description, category }] = useForm();
|
|
517
389
|
|
|
518
390
|
return (
|
|
519
|
-
<
|
|
391
|
+
<form {...form.props}>
|
|
520
392
|
<input
|
|
521
393
|
type="text"
|
|
522
|
-
name={
|
|
523
|
-
form={
|
|
524
|
-
defaultValue={
|
|
525
|
-
requried={
|
|
526
|
-
minLength={
|
|
527
|
-
maxLength={
|
|
528
|
-
min={
|
|
529
|
-
max={
|
|
530
|
-
multiple={
|
|
531
|
-
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={
|
|
535
|
-
form={
|
|
536
|
-
defaultValue={
|
|
537
|
-
requried={
|
|
538
|
-
minLength={
|
|
539
|
-
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
|
-
</
|
|
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
|
|