@conform-to/react 0.2.0 → 0.3.0-pre.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 +319 -228
- package/helpers.d.ts +4 -4
- package/helpers.js +28 -30
- package/hooks.d.ts +114 -35
- package/hooks.js +394 -318
- package/index.d.ts +1 -1
- package/index.js +4 -4
- package/module/helpers.js +28 -30
- package/module/hooks.js +396 -320
- package/module/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,76 +14,137 @@
|
|
|
14
14
|
|
|
15
15
|
### useForm
|
|
16
16
|
|
|
17
|
-
By default, the browser calls [reportValidity()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reportValidity) on the form element when
|
|
17
|
+
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.
|
|
18
18
|
|
|
19
|
-
This hook enhances
|
|
19
|
+
This hook enhances the form validation behaviour in 3 parts:
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
1. It lets you hook up custom validation logic into different form events. For example, revalidation will be triggered whenever something changed.
|
|
22
|
+
2. It provides options for you to decide the best timing to start reporting errors. This could be as earliest as the user start typing, or also as late as the user try submitting the form.
|
|
23
|
+
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.
|
|
24
24
|
|
|
25
25
|
```tsx
|
|
26
26
|
import { useForm } from '@conform-to/react';
|
|
27
27
|
|
|
28
|
-
function
|
|
28
|
+
function LoginForm() {
|
|
29
29
|
const formProps = useForm({
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* Define when the error should be reported initially.
|
|
32
|
+
* Support "onSubmit", "onChange", "onBlur".
|
|
33
|
+
*
|
|
34
|
+
* Default to `onSubmit`.
|
|
34
35
|
*/
|
|
35
36
|
initialReport: 'onBlur',
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
39
|
+
* Enable native validation before hydation.
|
|
40
|
+
*
|
|
41
|
+
* Default to `false`.
|
|
40
42
|
*/
|
|
41
|
-
fallbackNative:
|
|
43
|
+
fallbackNative: false,
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* `false`.
|
|
46
|
+
* Accept form submission regardless of the form validity.
|
|
47
|
+
*
|
|
48
|
+
* Default to `false`.
|
|
47
49
|
*/
|
|
48
50
|
noValidate: false,
|
|
49
51
|
|
|
50
52
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* It will NOT be called if
|
|
54
|
-
* (1) one of the fields is invalid, and
|
|
55
|
-
* (2) noValidate is set to false
|
|
53
|
+
* A function to be called when the form should be (re)validated.
|
|
56
54
|
*/
|
|
57
|
-
|
|
55
|
+
validate(form, submitter) {
|
|
58
56
|
// ...
|
|
59
57
|
},
|
|
60
58
|
|
|
61
59
|
/**
|
|
62
|
-
*
|
|
60
|
+
* The submit event handler of the form.
|
|
63
61
|
*/
|
|
64
|
-
|
|
62
|
+
onSubmit(event) {
|
|
65
63
|
// ...
|
|
66
64
|
},
|
|
67
65
|
});
|
|
68
66
|
|
|
69
|
-
return
|
|
67
|
+
return (
|
|
68
|
+
<form {...formProps}>
|
|
69
|
+
<input type="email" name="email" required />
|
|
70
|
+
<input type="password" name="password" required />
|
|
71
|
+
<button type="submit">Login</button>
|
|
72
|
+
</form>
|
|
73
|
+
);
|
|
70
74
|
}
|
|
71
75
|
```
|
|
72
76
|
|
|
73
77
|
<details>
|
|
74
78
|
<summary>What is `formProps`?</summary>
|
|
75
79
|
|
|
76
|
-
It is a group of properties required to
|
|
80
|
+
It is a group of properties properties required to hook into form events. They can also be set explicitly as shown below:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
function RandomForm() {
|
|
84
|
+
const formProps = useForm();
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<form
|
|
88
|
+
ref={formProps.ref}
|
|
89
|
+
onSubmit={formProps.onSubmit}
|
|
90
|
+
noValidate={formProps.noValidate}
|
|
91
|
+
>
|
|
92
|
+
{/* ... */}
|
|
93
|
+
</form>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
</details>
|
|
99
|
+
|
|
100
|
+
<details>
|
|
101
|
+
<summary>Does it work with custom form component like Remix Form?</summary>
|
|
102
|
+
|
|
103
|
+
Yes! It will fallback to native form submission if the submit event handler is omitted or the event is not default prevented.
|
|
77
104
|
|
|
78
105
|
```tsx
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
106
|
+
import { useFrom } from '@conform-to/react';
|
|
107
|
+
import { Form } from '@remix-run/react';
|
|
108
|
+
|
|
109
|
+
function LoginForm() {
|
|
110
|
+
const formProps = useForm();
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<Form method="post" action="/login" {...formProps}>
|
|
114
|
+
{/* ... */}
|
|
115
|
+
</Form>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
<details>
|
|
123
|
+
<summary>Is the `validate` function required?</summary>
|
|
124
|
+
|
|
125
|
+
The `validate` 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.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import { useForm, useFieldset } from '@conform-to/react';
|
|
129
|
+
|
|
130
|
+
function LoginForm() {
|
|
131
|
+
const formProps = useForm();
|
|
132
|
+
const { email, password } = useFieldset(formProps.ref);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<form {...formProps}>
|
|
136
|
+
<label>
|
|
137
|
+
<input type="email" name="email" required />
|
|
138
|
+
{email.error}
|
|
139
|
+
</label>
|
|
140
|
+
<label>
|
|
141
|
+
<input type="password" name="password" required />
|
|
142
|
+
{password.error}
|
|
143
|
+
</label>
|
|
144
|
+
<button type="submit">Login</button>
|
|
145
|
+
</form>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
87
148
|
```
|
|
88
149
|
|
|
89
150
|
</details>
|
|
@@ -92,199 +153,177 @@ It is a group of properties required to setup the form. They can also be set exp
|
|
|
92
153
|
|
|
93
154
|
### useFieldset
|
|
94
155
|
|
|
95
|
-
This hook
|
|
156
|
+
This hook can be used to monitor the state of each field and help fields configuration. It lets you:
|
|
157
|
+
|
|
158
|
+
1. Capturing errors at the form/fieldset level, removing the need to setup invalid handler on each field.
|
|
159
|
+
2. Defining config in one central place. e.g. name, default value and constraint, then distributing it to each field using the [conform](#conform) helpers.
|
|
96
160
|
|
|
97
161
|
```tsx
|
|
98
|
-
import { useFieldset } from '@conform-to/react';
|
|
162
|
+
import { useForm, useFieldset } from '@conform-to/react';
|
|
99
163
|
|
|
100
164
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* Defining a schema manually could be error-prone. It
|
|
104
|
-
* is strongly recommended to use a schema validation
|
|
105
|
-
* library with a schema resolver.
|
|
106
|
-
*
|
|
107
|
-
* Currently only Zod is supported and Yup support is
|
|
108
|
-
* coming soon. Please check the corresponding package
|
|
109
|
-
* for the setup required
|
|
165
|
+
* Consider the schema as follow:
|
|
110
166
|
*/
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
name: string;
|
|
116
|
-
isbn: string;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
*/
|
|
167
|
+
type Book = {
|
|
168
|
+
name: string;
|
|
169
|
+
isbn: string;
|
|
170
|
+
};
|
|
120
171
|
|
|
121
172
|
function BookFieldset() {
|
|
122
|
-
const
|
|
123
|
-
|
|
173
|
+
const formProps = useForm();
|
|
174
|
+
const { name, isbn } = useFieldset<Book>(
|
|
124
175
|
/**
|
|
125
|
-
*
|
|
126
|
-
* They are used to configure the field (input, select, textarea)
|
|
127
|
-
*
|
|
128
|
-
* Please check the docs of the `conform` helpers for how to
|
|
129
|
-
* use them together
|
|
176
|
+
* A ref object of the form element or fieldset element
|
|
130
177
|
*/
|
|
178
|
+
formProps.ref,
|
|
131
179
|
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Name of the fieldset
|
|
138
|
-
* Required only for nested fieldset.
|
|
139
|
-
*/
|
|
140
|
-
name: 'book',
|
|
180
|
+
/**
|
|
181
|
+
* The prefix used to generate the name of nested fields.
|
|
182
|
+
*/
|
|
183
|
+
name: 'book',
|
|
141
184
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
185
|
+
/**
|
|
186
|
+
* An object representing the initial value of the fieldset.
|
|
187
|
+
*/
|
|
188
|
+
defaultValue: {
|
|
189
|
+
isbn: '0340013818',
|
|
190
|
+
},
|
|
147
191
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
192
|
+
/**
|
|
193
|
+
* An object describing the initial error of each field
|
|
194
|
+
*/
|
|
195
|
+
initialError: {
|
|
196
|
+
isbn: 'Invalid ISBN',
|
|
197
|
+
},
|
|
154
198
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
199
|
+
/**
|
|
200
|
+
* An object describing the constraint of each field
|
|
201
|
+
*/
|
|
202
|
+
constraint: {
|
|
203
|
+
isbn: {
|
|
204
|
+
required: true,
|
|
205
|
+
pattern: '[0-9]{10,13}',
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The id of the form. This is required only if you
|
|
211
|
+
* are connecting each field to a form remotely.
|
|
212
|
+
*/
|
|
213
|
+
form: 'remote-form-id',
|
|
160
214
|
},
|
|
161
|
-
|
|
215
|
+
);
|
|
162
216
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Latest error of the field
|
|
219
|
+
* This would be 'Invalid ISBN' initially as specified
|
|
220
|
+
* in the initialError config
|
|
221
|
+
*/
|
|
222
|
+
console.log(book.error);
|
|
169
223
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
224
|
+
/**
|
|
225
|
+
* This would be `book.isbn` instead of `isbn`
|
|
226
|
+
* if the `name` option is provided
|
|
227
|
+
*/
|
|
228
|
+
console.log(book.config.name);
|
|
175
229
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
230
|
+
/**
|
|
231
|
+
* This would be `0340013818` if specified
|
|
232
|
+
* on the `initalValue` option
|
|
233
|
+
*/
|
|
234
|
+
console.log(book.config.defaultValue);
|
|
181
235
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
error,
|
|
236
|
+
/**
|
|
237
|
+
* Initial error message
|
|
238
|
+
* This would be 'Invalid ISBN' if specified
|
|
239
|
+
*/
|
|
240
|
+
console.log(book.config.initialError);
|
|
188
241
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
* required: true,
|
|
195
|
-
* pattern: '[0-9]{10,13}'
|
|
196
|
-
* }
|
|
197
|
-
*/
|
|
198
|
-
...constraint,
|
|
199
|
-
} = isbn;
|
|
242
|
+
/**
|
|
243
|
+
* This would be `random-form-id`
|
|
244
|
+
* because of the `form` option provided
|
|
245
|
+
*/
|
|
246
|
+
console.log(book.config.form);
|
|
200
247
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Constraint of the field (required, minLength etc)
|
|
250
|
+
*
|
|
251
|
+
* For example, the constraint of the isbn field would be:
|
|
252
|
+
* {
|
|
253
|
+
* required: true,
|
|
254
|
+
* pattern: '[0-9]{10,13}'
|
|
255
|
+
* }
|
|
256
|
+
*/
|
|
257
|
+
console.log(book.config.required);
|
|
258
|
+
console.log(book.config.pattern);
|
|
259
|
+
|
|
260
|
+
return <form {...formProps}>{/* ... */}</form>;
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
If you don't have direct access to the form ref, you can also pass a fieldset ref.
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
import { useFieldset } from '@conform-to/react';
|
|
268
|
+
import { useRef } from 'react';
|
|
269
|
+
|
|
270
|
+
function Fieldset() {
|
|
271
|
+
const ref = useRef();
|
|
272
|
+
const fieldset = useFieldset(ref);
|
|
273
|
+
|
|
274
|
+
return <fieldset ref={ref}>{/* ... */}</fieldset>;
|
|
206
275
|
}
|
|
207
276
|
```
|
|
208
277
|
|
|
209
278
|
<details>
|
|
210
|
-
|
|
279
|
+
<summary>Is it required to provide the FieldsetConfig to `useFieldset`?</summary>
|
|
211
280
|
|
|
212
|
-
|
|
281
|
+
No. The only thing required is the ref object. All the config is optional. You can always pass them to each fields manually.
|
|
213
282
|
|
|
214
283
|
```tsx
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
284
|
+
import { useForm, useFieldset } from '@conform-to/react';
|
|
285
|
+
|
|
286
|
+
function SubscriptionForm() {
|
|
287
|
+
const formProps = useForm();
|
|
288
|
+
const { email } = useFieldset(formProps.ref);
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<form {...formProps}>
|
|
292
|
+
<input
|
|
293
|
+
type="email"
|
|
294
|
+
name={email.config.name}
|
|
295
|
+
defaultValue="support@conform.dev"
|
|
296
|
+
required
|
|
297
|
+
/>
|
|
298
|
+
</form>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
224
301
|
```
|
|
225
302
|
|
|
226
303
|
</details>
|
|
227
304
|
|
|
228
305
|
<details>
|
|
229
|
-
<summary>
|
|
306
|
+
<summary>Why does `useFieldset` require a ref object of the form or fieldset?</summary>
|
|
230
307
|
|
|
231
|
-
|
|
232
|
-
import type { Schema } from '@conform-to/react';
|
|
308
|
+
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.
|
|
233
309
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
name: string;
|
|
239
|
-
isbn: string;
|
|
240
|
-
quantity?: number;
|
|
241
|
-
}> = {
|
|
242
|
-
/**
|
|
243
|
-
* Define the fields with its constraint together
|
|
244
|
-
*/
|
|
245
|
-
fields: {
|
|
246
|
-
name: {
|
|
247
|
-
required: true,
|
|
248
|
-
},
|
|
249
|
-
isbn: {
|
|
250
|
-
required: true,
|
|
251
|
-
minLength: 10,
|
|
252
|
-
maxLength: 13,
|
|
253
|
-
pattern: '[0-9]{10,13}',
|
|
254
|
-
},
|
|
255
|
-
quantity: {
|
|
256
|
-
min: '0',
|
|
257
|
-
},
|
|
258
|
-
},
|
|
310
|
+
```tsx
|
|
311
|
+
function ExampleForm() {
|
|
312
|
+
const formRef = useRef();
|
|
313
|
+
const inputRef = useRef();
|
|
259
314
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Lookup the field elements using the fieldset element
|
|
267
|
-
*/
|
|
268
|
-
const [name] = getFieldElements(fieldset, 'name');
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
// Both statements will log `true`
|
|
317
|
+
console.log(formRef.current === inputRef.current.form);
|
|
318
|
+
console.log(formRef.current.elements.namedItem('title') === inputRef.current)
|
|
319
|
+
}, []);
|
|
269
320
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Setting error message based on custom constraint
|
|
278
|
-
*/
|
|
279
|
-
name.setCustomValidity('Please enter a valid name');
|
|
280
|
-
} else {
|
|
281
|
-
/**
|
|
282
|
-
* Clearing the error message (Important!)
|
|
283
|
-
*/
|
|
284
|
-
name.setCustomValidity('');
|
|
285
|
-
}
|
|
286
|
-
},
|
|
287
|
-
};
|
|
321
|
+
return (
|
|
322
|
+
<form ref={formRef}>
|
|
323
|
+
<input ref={inputRef} name="title">
|
|
324
|
+
</form>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
288
327
|
```
|
|
289
328
|
|
|
290
329
|
</details>
|
|
@@ -293,32 +332,77 @@ const bookSchema: Schema<{
|
|
|
293
332
|
|
|
294
333
|
### useFieldList
|
|
295
334
|
|
|
296
|
-
|
|
335
|
+
It returns a list of key and config, with a group of helpers configuring buttons for list manipulation
|
|
297
336
|
|
|
298
337
|
```tsx
|
|
299
338
|
import { useFieldset, useFieldList } from '@conform-to/react';
|
|
339
|
+
import { useRef } from 'react';
|
|
300
340
|
|
|
301
341
|
/**
|
|
302
342
|
* Consider the schema as follow:
|
|
303
|
-
*
|
|
304
|
-
* type Collection = {
|
|
305
|
-
* books: Array<{ name: string; isbn: string; }>
|
|
306
|
-
* }
|
|
307
343
|
*/
|
|
344
|
+
type Book = {
|
|
345
|
+
name: string;
|
|
346
|
+
isbn: string;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
type Collection = {
|
|
350
|
+
books: Book[];
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
function CollectionFieldset() {
|
|
354
|
+
const ref = useRef();
|
|
355
|
+
const { books } = useFieldset<Collection>(ref);
|
|
356
|
+
const [bookList, control] = useFieldList(ref, books);
|
|
357
|
+
|
|
358
|
+
return (
|
|
359
|
+
<fieldset ref={ref}>
|
|
360
|
+
{bookList.map((book, index) => (
|
|
361
|
+
<div key={book.key}>
|
|
362
|
+
{/* To setup the fields */}
|
|
363
|
+
<input
|
|
364
|
+
name={`${book.config.name}.name`}
|
|
365
|
+
defaultValue={book.config.defaultValue.name}
|
|
366
|
+
/>
|
|
367
|
+
<input
|
|
368
|
+
name={`${book.config.name}.isbn`}
|
|
369
|
+
defaultValue={book.config.defaultValue.isbn}
|
|
370
|
+
/>
|
|
371
|
+
|
|
372
|
+
{/* To setup a delete button */}
|
|
373
|
+
<button {...control.remove({ index })}>Delete</button>
|
|
374
|
+
</div>
|
|
375
|
+
))}
|
|
376
|
+
|
|
377
|
+
{/* To setup a button that can append a new row with optional default value */}
|
|
378
|
+
<button {...control.append({ defaultValue: { name: '', isbn: '' } })}>
|
|
379
|
+
add
|
|
380
|
+
</button>
|
|
381
|
+
</fieldset>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
This hook can also be used in combination with `useFieldset` to distribute the config:
|
|
387
|
+
|
|
388
|
+
```tsx
|
|
389
|
+
import { useForm, useFieldset, useFieldList } from '@conform-to/react';
|
|
390
|
+
import { useRef } from 'react';
|
|
308
391
|
|
|
309
|
-
function
|
|
310
|
-
const
|
|
311
|
-
const
|
|
392
|
+
function CollectionFieldset() {
|
|
393
|
+
const ref = useRef();
|
|
394
|
+
const { books } = useFieldset<Collection>(ref);
|
|
395
|
+
const [bookList, control] = useFieldList(ref, books);
|
|
312
396
|
|
|
313
397
|
return (
|
|
314
|
-
<fieldset {
|
|
398
|
+
<fieldset ref={ref}>
|
|
315
399
|
{bookList.map((book, index) => (
|
|
316
400
|
<div key={book.key}>
|
|
317
|
-
{/* `book.props` is a
|
|
318
|
-
<BookFieldset {...book.
|
|
401
|
+
{/* `book.props` is a FieldConfig object similar to `books` */}
|
|
402
|
+
<BookFieldset {...book.config}>
|
|
319
403
|
|
|
320
404
|
{/* To setup a delete button */}
|
|
321
|
-
<button {...control.remove(index)}>Delete</button>
|
|
405
|
+
<button {...control.remove({ index })}>Delete</button>
|
|
322
406
|
</div>
|
|
323
407
|
))}
|
|
324
408
|
|
|
@@ -334,7 +418,8 @@ function CollectionForm() {
|
|
|
334
418
|
* options with the component props instead
|
|
335
419
|
*/
|
|
336
420
|
function BookFieldset({ name, form, defaultValue, error }) {
|
|
337
|
-
const
|
|
421
|
+
const ref = useRef();
|
|
422
|
+
const { name, isbn } = useFieldset(ref, {
|
|
338
423
|
name,
|
|
339
424
|
form,
|
|
340
425
|
defaultValue,
|
|
@@ -342,7 +427,7 @@ function BookFieldset({ name, form, defaultValue, error }) {
|
|
|
342
427
|
});
|
|
343
428
|
|
|
344
429
|
return (
|
|
345
|
-
<fieldset {
|
|
430
|
+
<fieldset ref={ref}>
|
|
346
431
|
{/* ... */}
|
|
347
432
|
</fieldset>
|
|
348
433
|
);
|
|
@@ -354,19 +439,19 @@ function BookFieldset({ name, form, defaultValue, error }) {
|
|
|
354
439
|
|
|
355
440
|
```tsx
|
|
356
441
|
// To append a new row with optional defaultValue
|
|
357
|
-
<button {...controls.append(defaultValue)}>Append</button>;
|
|
442
|
+
<button {...controls.append({ defaultValue })}>Append</button>;
|
|
358
443
|
|
|
359
444
|
// To prepend a new row with optional defaultValue
|
|
360
|
-
<button {...controls.prepend(defaultValue)}>Prepend</button>;
|
|
445
|
+
<button {...controls.prepend({ defaultValue })}>Prepend</button>;
|
|
361
446
|
|
|
362
447
|
// To remove a row by index
|
|
363
|
-
<button {...controls.remove(index)}>Remove</button>;
|
|
448
|
+
<button {...controls.remove({ index })}>Remove</button>;
|
|
364
449
|
|
|
365
450
|
// To replace a row with another defaultValue
|
|
366
|
-
<button {...controls.replace(index, defaultValue)}>Replace</button>;
|
|
451
|
+
<button {...controls.replace({ index, defaultValue })}>Replace</button>;
|
|
367
452
|
|
|
368
453
|
// To reorder a particular row to an another index
|
|
369
|
-
<button {...controls.reorder(
|
|
454
|
+
<button {...controls.reorder({ from, to })}>Reorder</button>;
|
|
370
455
|
```
|
|
371
456
|
|
|
372
457
|
</details>
|
|
@@ -375,29 +460,32 @@ function BookFieldset({ name, form, defaultValue, error }) {
|
|
|
375
460
|
|
|
376
461
|
### useControlledInput
|
|
377
462
|
|
|
378
|
-
|
|
463
|
+
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.
|
|
379
464
|
|
|
380
465
|
```tsx
|
|
381
|
-
import { useControlledInput } from '@conform-to/react';
|
|
466
|
+
import { useFieldset, useControlledInput } from '@conform-to/react';
|
|
382
467
|
import { Select, MenuItem } from '@mui/material';
|
|
468
|
+
import { useRef } from 'react';
|
|
383
469
|
|
|
384
|
-
function
|
|
385
|
-
const
|
|
386
|
-
const
|
|
470
|
+
function MuiForm() {
|
|
471
|
+
const ref = useRef();
|
|
472
|
+
const { category } = useFieldset(schema);
|
|
473
|
+
const [inputProps, control] = useControlledInput(category);
|
|
387
474
|
|
|
388
475
|
return (
|
|
389
|
-
<fieldset {
|
|
390
|
-
{/* Render
|
|
391
|
-
{
|
|
476
|
+
<fieldset ref={ref}>
|
|
477
|
+
{/* Render a shadow input somewhere */}
|
|
478
|
+
<input {...inputProps} />
|
|
392
479
|
|
|
393
480
|
{/* MUI Select is a controlled component */}
|
|
394
481
|
<Select
|
|
395
482
|
label="Category"
|
|
396
|
-
value={control.value
|
|
397
|
-
onChange={
|
|
398
|
-
onBlur={
|
|
399
|
-
|
|
400
|
-
|
|
483
|
+
value={control.value}
|
|
484
|
+
onChange={control.onChange}
|
|
485
|
+
onBlur={control.onBlur}
|
|
486
|
+
inputProps={{
|
|
487
|
+
onInvalid: control.onInvalid
|
|
488
|
+
}}
|
|
401
489
|
>
|
|
402
490
|
<MenuItem value="">Please select</MenuItem>
|
|
403
491
|
<MenuItem value="a">Category A</MenuItem>
|
|
@@ -413,16 +501,18 @@ function RandomFieldset() {
|
|
|
413
501
|
|
|
414
502
|
### conform
|
|
415
503
|
|
|
416
|
-
It provides several helpers to
|
|
504
|
+
It provides several helpers to configure a native input field quickly:
|
|
417
505
|
|
|
418
506
|
```tsx
|
|
419
|
-
import { conform } from '@conform-to/react';
|
|
507
|
+
import { useFieldset, conform } from '@conform-to/react';
|
|
508
|
+
import { useRef } from 'react';
|
|
420
509
|
|
|
421
510
|
function RandomForm() {
|
|
422
|
-
const
|
|
511
|
+
const ref = useRef();
|
|
512
|
+
const { cateogry } = useFieldset(ref);
|
|
423
513
|
|
|
424
514
|
return (
|
|
425
|
-
<fieldset {
|
|
515
|
+
<fieldset ref={ref}>
|
|
426
516
|
<input {...conform.input(cateogry, { type: 'text' })} />
|
|
427
517
|
<textarea {...conform.textarea(cateogry)} />
|
|
428
518
|
<select {...conform.select(cateogry)}>{/* ... */}</select>
|
|
@@ -435,10 +525,11 @@ This is equivalent to:
|
|
|
435
525
|
|
|
436
526
|
```tsx
|
|
437
527
|
function RandomForm() {
|
|
438
|
-
const
|
|
528
|
+
const ref = useRef();
|
|
529
|
+
const { cateogry } = useFieldset(ref);
|
|
439
530
|
|
|
440
531
|
return (
|
|
441
|
-
<fieldset {
|
|
532
|
+
<fieldset ref={ref}>
|
|
442
533
|
<input
|
|
443
534
|
type="text"
|
|
444
535
|
name={cateogry.name}
|