@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 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 you submit the form. This checks the validity of all the fields in it and reports if there are errors through the bubbles.
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 this behaviour by allowing the developers to decide the best timing to start reporting errors using the `initialReport` option. This could start as earliest as the user typing or as late as the user submit the form.
19
+ This hook enhances the form validation behaviour in 3 parts:
20
20
 
21
- But, setting `initialReport` to `onSubmit` still works different from the native browser behaviour, which basically calls `reportValidity()` only at the time a submit event is received. The `useForm` hook introduces a **touched** state to each fields. It will eagerly report the validity of the field once it is touched. Any errors reported later will be updated as soon as new errors are found.
22
-
23
- Feel free to **SKIP** this if the native browser behaviour fullfills your need.
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 RandomForm() {
28
+ function LoginForm() {
29
29
  const formProps = useForm({
30
30
  /**
31
- * Decide when the error should be reported initially.
32
- * The options are `onSubmit`, `onBlur` or `onChange`.
33
- * Default to `onSubmit`
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
- * Native browser report will be enabled before hydation
39
- * if this is set to `true`. Default to `false`.
39
+ * Enable native validation before hydation.
40
+ *
41
+ * Default to `false`.
40
42
  */
41
- fallbackNative: true,
43
+ fallbackNative: false,
42
44
 
43
45
  /**
44
- * The form could be submitted regardless of the validity
45
- * of the form if this is set to `true`. Default to
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
- * Form submit handler
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
- onSubmit(e) {
55
+ validate(form, submitter) {
58
56
  // ...
59
57
  },
60
58
 
61
59
  /**
62
- * Form reset handler
60
+ * The submit event handler of the form.
63
61
  */
64
- onReset(e) {
62
+ onSubmit(event) {
65
63
  // ...
66
64
  },
67
65
  });
68
66
 
69
- return <form {...formProps}>{/* ... */}</form>;
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 setup the form. They can also be set explicitly as shown below:
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
- <form
80
- ref={formProps.ref}
81
- onSubmit={formProps.onSubmit}
82
- onReset={formProps.onReset}
83
- noValidate={formProps.noValidate}
84
- >
85
- {/* ... */}
86
- </form>
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 prepares all the config you need to setup the fieldset based on the provided schema.
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
- * Schema of the fieldset
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
- const schema = /*
112
- Assuming this to be a schema for book and it looks like this:
113
-
114
- type Book = {
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
- fieldsetProps,
173
+ const formProps = useForm();
174
+ const { name, isbn } = useFieldset<Book>(
124
175
  /**
125
- * The variables `name` and `isbn` are FieldProps objects
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
- name,
133
- isbn,
134
- },
135
- ] = useFieldset(schema, {
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
- * Id of the form
144
- * Required only if the fieldset is placed out of the form
145
- */
146
- form: 'random-form-id',
185
+ /**
186
+ * An object representing the initial value of the fieldset.
187
+ */
188
+ defaultValue: {
189
+ isbn: '0340013818',
190
+ },
147
191
 
148
- /**
149
- * Default value of the fieldset
150
- */
151
- defaultValue: {
152
- isbn: '0340013818',
153
- },
192
+ /**
193
+ * An object describing the initial error of each field
194
+ */
195
+ initialError: {
196
+ isbn: 'Invalid ISBN',
197
+ },
154
198
 
155
- /**
156
- * Error reported by the server
157
- */
158
- error: {
159
- isbn: 'Invalid ISBN',
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
- const {
164
- /**
165
- * This would be `book.isbn` instead of `isbn`
166
- * if the `name` option is provided
167
- */
168
- name,
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
- * This would be `random-form-id`
172
- * because of the `form` option provided
173
- */
174
- form,
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
- * This would be `0340013818` if specified
178
- * on the `initalValue` option
179
- */
180
- defaultValue,
230
+ /**
231
+ * This would be `0340013818` if specified
232
+ * on the `initalValue` option
233
+ */
234
+ console.log(book.config.defaultValue);
181
235
 
182
- /**
183
- * Current error message
184
- * This would be 'Invalid ISBN' initially if specified
185
- * on the `error` option
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
- * Constraint of the field (required, minLength etc)
191
- *
192
- * For example, the constraint of the isbn field could be:
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
- return (
202
- <fieldset {...fieldsetProps}>
203
- {/* ... */}
204
- </fieldset>)
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
- <summary>What is `fieldsetProps`?</summary>
279
+ <summary>Is it required to provide the FieldsetConfig to `useFieldset`?</summary>
211
280
 
212
- It is a group of properties required to setup the fieldset. They can also be set explicitly as shown below:
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
- <fieldset
216
- ref={fieldsetProps.ref}
217
- name={fieldsetProps.name}
218
- form={fieldsetProps.form}
219
- onInput={fieldsetProps.onInput}
220
- onInvalid={fieldsetProps.onInvalid}
221
- >
222
- {/* ... */}
223
- </fieldset>
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>How is a schema looks like?</summary>
306
+ <summary>Why does `useFieldset` require a ref object of the form or fieldset?</summary>
230
307
 
231
- ```tsx
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
- * Defining a schema manually
236
- */
237
- const bookSchema: Schema<{
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
- * Customise validation behaviour
262
- * Fallbacks to native browser validation if not specified
263
- */
264
- validate(fieldset) {
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
- if (name.validity.valueMissing) {
271
- /**
272
- * Setting error message based on validity
273
- */
274
- name.setCustomValidity('Required');
275
- } else if (name.value === 'something') {
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
- This hook is used in combination with `useFieldset` to handle array structure:
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 CollectionForm() {
310
- const [fieldsetProps, { books }] = useFieldset(collectionSchema);
311
- const [bookList, control] = useFieldList(books);
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 {...fieldsetProps}>
398
+ <fieldset ref={ref}>
315
399
  {bookList.map((book, index) => (
316
400
  <div key={book.key}>
317
- {/* `book.props` is a FieldProps object similar to `books` */}
318
- <BookFieldset {...book.props}>
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 [fieldsetProps, { name, isbn }] = useFieldset(bookSchema, {
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 {...fieldsetProps}>
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(fromIndex, toIndex)}>Reorder</button>;
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
- This hooks creates a shadow input that would be used to validate against the schema. Mainly used to get around problem integrating with controlled component.
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 RandomFieldset() {
385
- const [fieldsetProps, { category }] = useFieldset(schema);
386
- const [input, control] = useControlledInput(category);
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 {...fieldsetProps}>
390
- {/* Render the shadow input somewhere within the fieldset */}
391
- {input}
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={(e) => control.onChange(e.target.value)}
398
- onBlur={() => control.onBlur()}
399
- error={Boolean(category.error)}
400
- helperText={category.error}
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 setup a native input field quickly:
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 [setupFieldset, { cateogry }] = useFieldset(/* ... */);
511
+ const ref = useRef();
512
+ const { cateogry } = useFieldset(ref);
423
513
 
424
514
  return (
425
- <fieldset {...setupFieldset}>
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 [setupFieldset, { cateogry }] = useFieldset(/* ... */);
528
+ const ref = useRef();
529
+ const { cateogry } = useFieldset(ref);
439
530
 
440
531
  return (
441
- <fieldset {...setupFieldset}>
532
+ <fieldset ref={ref}>
442
533
  <input
443
534
  type="text"
444
535
  name={cateogry.name}