@conform-to/react 0.3.0 → 0.4.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
@@ -2,15 +2,18 @@
2
2
 
3
3
  > [React](https://github.com/facebook/react) adapter for [conform](https://github.com/edmundhung/conform)
4
4
 
5
+ <!-- aside -->
6
+
5
7
  ## API Reference
6
8
 
7
- - [useForm](#useForm)
8
- - [useFieldset](#useFieldset)
9
- - [useFieldList](#useFieldList)
10
- - [useControlledInput](#useControlledInput)
9
+ - [useForm](#useform)
10
+ - [useFieldset](#usefieldset)
11
+ - [useFieldList](#usefieldlist)
12
+ - [useControlledInput](#usecontrolledinput)
13
+ - [createValidate](#createvalidate)
11
14
  - [conform](#conform)
12
15
 
13
- ---
16
+ <!-- /aside -->
14
17
 
15
18
  ### useForm
16
19
 
@@ -219,31 +222,31 @@ function BookFieldset() {
219
222
  * This would be 'Invalid ISBN' initially as specified
220
223
  * in the initialError config
221
224
  */
222
- console.log(book.error);
225
+ console.log(isbn.error);
223
226
 
224
227
  /**
225
228
  * This would be `book.isbn` instead of `isbn`
226
229
  * if the `name` option is provided
227
230
  */
228
- console.log(book.config.name);
231
+ console.log(isbn.config.name);
229
232
 
230
233
  /**
231
234
  * This would be `0340013818` if specified
232
235
  * on the `initalValue` option
233
236
  */
234
- console.log(book.config.defaultValue);
237
+ console.log(isbn.config.defaultValue);
235
238
 
236
239
  /**
237
240
  * Initial error message
238
241
  * This would be 'Invalid ISBN' if specified
239
242
  */
240
- console.log(book.config.initialError);
243
+ console.log(isbn.config.initialError);
241
244
 
242
245
  /**
243
246
  * This would be `random-form-id`
244
247
  * because of the `form` option provided
245
248
  */
246
- console.log(book.config.form);
249
+ console.log(isbn.config.form);
247
250
 
248
251
  /**
249
252
  * Constraint of the field (required, minLength etc)
@@ -254,8 +257,8 @@ function BookFieldset() {
254
257
  * pattern: '[0-9]{10,13}'
255
258
  * }
256
259
  */
257
- console.log(book.config.required);
258
- console.log(book.config.pattern);
260
+ console.log(isbn.config.required);
261
+ console.log(isbn.config.pattern);
259
262
 
260
263
  return <form {...formProps}>{/* ... */}</form>;
261
264
  }
@@ -353,7 +356,7 @@ type Collection = {
353
356
  function CollectionFieldset() {
354
357
  const ref = useRef();
355
358
  const { books } = useFieldset<Collection>(ref);
356
- const [bookList, control] = useFieldList(ref, books);
359
+ const [bookList, control] = useFieldList(ref, books.config);
357
360
 
358
361
  return (
359
362
  <fieldset ref={ref}>
@@ -392,14 +395,14 @@ import { useRef } from 'react';
392
395
  function CollectionFieldset() {
393
396
  const ref = useRef();
394
397
  const { books } = useFieldset<Collection>(ref);
395
- const [bookList, control] = useFieldList(ref, books);
398
+ const [bookList, control] = useFieldList(ref, books.config);
396
399
 
397
400
  return (
398
401
  <fieldset ref={ref}>
399
402
  {bookList.map((book, index) => (
400
403
  <div key={book.key}>
401
- {/* `book.props` is a FieldConfig object similar to `books` */}
402
- <BookFieldset {...book.config}>
404
+ {/* `book.config` is a FieldConfig object similar to `books` */}
405
+ <BookFieldset {...book.config} />
403
406
 
404
407
  {/* To setup a delete button */}
405
408
  <button {...control.remove({ index })}>Delete</button>
@@ -426,11 +429,7 @@ function BookFieldset({ name, form, defaultValue, error }) {
426
429
  error,
427
430
  });
428
431
 
429
- return (
430
- <fieldset ref={ref}>
431
- {/* ... */}
432
- </fieldset>
433
- );
432
+ return <fieldset ref={ref}>{/* ... */}</fieldset>;
434
433
  }
435
434
  ```
436
435
 
@@ -470,7 +469,7 @@ import { useRef } from 'react';
470
469
  function MuiForm() {
471
470
  const ref = useRef();
472
471
  const { category } = useFieldset(schema);
473
- const [inputProps, control] = useControlledInput(category);
472
+ const [inputProps, control] = useControlledInput(category.config);
474
473
 
475
474
  return (
476
475
  <fieldset ref={ref}>
@@ -480,6 +479,7 @@ function MuiForm() {
480
479
  {/* MUI Select is a controlled component */}
481
480
  <Select
482
481
  label="Category"
482
+ inputRef={control.ref}
483
483
  value={control.value}
484
484
  onChange={control.onChange}
485
485
  onBlur={control.onBlur}
@@ -499,6 +499,57 @@ function MuiForm() {
499
499
 
500
500
  ---
501
501
 
502
+ ### createValidate
503
+
504
+ This help you configure a validate function to check the validity of each fields and setup custom messages using the Constraint Validation APIs.
505
+
506
+ ```tsx
507
+ import { useForm, createValidate } from '@conform-to/react';
508
+
509
+ export default function SignupForm() {
510
+ const formProps = useForm({
511
+ validate: createValidate((field, formData) => {
512
+ switch (field.name) {
513
+ case 'email':
514
+ if (field.validity.valueMissing) {
515
+ field.setCustomValidity('Email is required');
516
+ } else if (field.validity.typeMismatch) {
517
+ field.setCustomValidity('Please enter a valid email');
518
+ } else {
519
+ field.setCustomValidity('');
520
+ }
521
+ break;
522
+ case 'password':
523
+ if (field.validity.valueMissing) {
524
+ field.setCustomValidity('Password is required');
525
+ } else if (field.validity.tooShort) {
526
+ field.setCustomValidity(
527
+ 'The password should be at least 10 characters long',
528
+ );
529
+ } else {
530
+ field.setCustomValidity('');
531
+ }
532
+ break;
533
+ case 'confirm-password': {
534
+ if (field.validity.valueMissing) {
535
+ field.setCustomValidity('Confirm Password is required');
536
+ } else if (field.value !== formData.get('password')) {
537
+ field.setCustomValidity('The password does not match');
538
+ } else {
539
+ field.setCustomValidity('');
540
+ }
541
+ break;
542
+ }
543
+ }
544
+ }),
545
+ });
546
+
547
+ return <form {...formProps}>{/* ... */}</form>;
548
+ }
549
+ ```
550
+
551
+ ---
552
+
502
553
  ### conform
503
554
 
504
555
  It provides several helpers to configure a native input field quickly:
@@ -509,13 +560,13 @@ import { useRef } from 'react';
509
560
 
510
561
  function RandomForm() {
511
562
  const ref = useRef();
512
- const { cateogry } = useFieldset(ref);
563
+ const { category } = useFieldset(ref);
513
564
 
514
565
  return (
515
566
  <fieldset ref={ref}>
516
- <input {...conform.input(cateogry, { type: 'text' })} />
517
- <textarea {...conform.textarea(cateogry)} />
518
- <select {...conform.select(cateogry)}>{/* ... */}</select>
567
+ <input {...conform.input(category.config, { type: 'text' })} />
568
+ <textarea {...conform.textarea(category.config)} />
569
+ <select {...conform.select(category.config)}>{/* ... */}</select>
519
570
  </fieldset>
520
571
  );
521
572
  }
@@ -526,37 +577,37 @@ This is equivalent to:
526
577
  ```tsx
527
578
  function RandomForm() {
528
579
  const ref = useRef();
529
- const { cateogry } = useFieldset(ref);
580
+ const { category } = useFieldset(ref);
530
581
 
531
582
  return (
532
583
  <fieldset ref={ref}>
533
584
  <input
534
585
  type="text"
535
- name={cateogry.name}
536
- form={cateogry.form}
537
- defaultValue={cateogry.defaultValue}
538
- requried={cateogry.required}
539
- minLength={cateogry.minLength}
540
- maxLength={cateogry.maxLength}
541
- min={cateogry.min}
542
- max={cateogry.max}
543
- multiple={cateogry.multiple}
544
- pattern={cateogry.pattern}
586
+ name={category.config.name}
587
+ form={category.config.form}
588
+ defaultValue={category.config.defaultValue}
589
+ requried={category.config.required}
590
+ minLength={category.config.minLength}
591
+ maxLength={category.config.maxLength}
592
+ min={category.config.min}
593
+ max={category.config.max}
594
+ multiple={category.config.multiple}
595
+ pattern={category.config.pattern}
545
596
  >
546
597
  <textarea
547
- name={cateogry.name}
548
- form={cateogry.form}
549
- defaultValue={cateogry.defaultValue}
550
- requried={cateogry.required}
551
- minLength={cateogry.minLength}
552
- maxLength={cateogry.maxLength}
598
+ name={category.config.name}
599
+ form={category.config.form}
600
+ defaultValue={category.config.defaultValue}
601
+ requried={category.config.required}
602
+ minLength={category.config.minLength}
603
+ maxLength={category.config.maxLength}
553
604
  />
554
605
  <select
555
- name={cateogry.name}
556
- form={cateogry.form}
557
- defaultValue={cateogry.defaultValue}
558
- requried={cateogry.required}
559
- multiple={cateogry.multiple}
606
+ name={category.config.name}
607
+ form={category.config.form}
608
+ defaultValue={category.config.defaultValue}
609
+ requried={category.config.required}
610
+ multiple={category.config.multiple}
560
611
  >
561
612
  {/* ... */}
562
613
  </select>
package/helpers.js CHANGED
@@ -22,6 +22,10 @@ function input(config) {
22
22
  multiple: config.multiple
23
23
  };
24
24
 
25
+ if (config.initialError && config.initialError.length > 0) {
26
+ attributes.autoFocus = true;
27
+ }
28
+
25
29
  if (isCheckboxOrRadio) {
26
30
  attributes.value = value !== null && value !== void 0 ? value : 'on';
27
31
  attributes.defaultChecked = config.defaultValue === attributes.value;
@@ -34,25 +38,38 @@ function input(config) {
34
38
  function select(config) {
35
39
  var _config$defaultValue;
36
40
 
37
- return {
41
+ var attributes = {
38
42
  name: config.name,
39
43
  form: config.form,
40
44
  defaultValue: config.multiple ? Array.isArray(config.defaultValue) ? config.defaultValue : [] : "".concat((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : ''),
41
45
  required: config.required,
42
46
  multiple: config.multiple
43
47
  };
48
+
49
+ if (config.initialError && config.initialError.length > 0) {
50
+ attributes.autoFocus = true;
51
+ }
52
+
53
+ return attributes;
44
54
  }
45
55
  function textarea(config) {
46
56
  var _config$defaultValue2;
47
57
 
48
- return {
58
+ var attributes = {
49
59
  name: config.name,
50
60
  form: config.form,
51
61
  defaultValue: "".concat((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : ''),
52
62
  required: config.required,
53
63
  minLength: config.minLength,
54
- maxLength: config.maxLength
64
+ maxLength: config.maxLength,
65
+ autoFocus: Boolean(config.initialError)
55
66
  };
67
+
68
+ if (config.initialError && config.initialError.length > 0) {
69
+ attributes.autoFocus = true;
70
+ }
71
+
72
+ return attributes;
56
73
  }
57
74
 
58
75
  exports.input = input;
package/hooks.d.ts CHANGED
@@ -1,6 +1,11 @@
1
- import { type FieldConfig, type FieldError, type FieldValue, type FieldElement, type FieldsetConstraint, type ListCommand, type Primitive } from '@conform-to/dom';
1
+ import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type FormState, type ListCommand, type Primitive, type Submission } from '@conform-to/dom';
2
2
  import { type InputHTMLAttributes, type FormEvent, type RefObject } from 'react';
3
- export interface FormConfig {
3
+ interface FormContext<Schema extends Record<string, any>> {
4
+ form: HTMLFormElement;
5
+ formData: FormData;
6
+ submission: Submission<Schema>;
7
+ }
8
+ export interface FormConfig<Schema extends Record<string, any>> {
4
9
  /**
5
10
  * Define when the error should be reported initially.
6
11
  * Support "onSubmit", "onChange", "onBlur".
@@ -8,6 +13,14 @@ export interface FormConfig {
8
13
  * Default to `onSubmit`.
9
14
  */
10
15
  initialReport?: 'onSubmit' | 'onChange' | 'onBlur';
16
+ /**
17
+ * An object representing the initial value of the form.
18
+ */
19
+ defaultValue?: FieldValue<Schema>;
20
+ /**
21
+ * An object describing the state from the last submission
22
+ */
23
+ state?: FormState<Schema>;
11
24
  /**
12
25
  * Enable native validation before hydation.
13
26
  *
@@ -23,12 +36,12 @@ export interface FormConfig {
23
36
  /**
24
37
  * A function to be called when the form should be (re)validated.
25
38
  */
26
- validate?: (form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null) => void;
39
+ onValidate?: (context: FormContext<Schema>) => boolean;
27
40
  /**
28
41
  * The submit event handler of the form. It will be called
29
42
  * only when the form is considered valid.
30
43
  */
31
- onSubmit?: (event: FormEvent<HTMLFormElement>) => void;
44
+ onSubmit?: (event: FormEvent<HTMLFormElement>, context: FormContext<Schema>) => void;
32
45
  }
33
46
  /**
34
47
  * Properties to be applied to the form element
@@ -38,13 +51,19 @@ interface FormProps {
38
51
  onSubmit: (event: FormEvent<HTMLFormElement>) => void;
39
52
  noValidate: boolean;
40
53
  }
54
+ interface Form<Schema extends Record<string, any>> {
55
+ ref: RefObject<HTMLFormElement>;
56
+ error: string;
57
+ props: FormProps;
58
+ config: FieldsetConfig<Schema>;
59
+ }
41
60
  /**
42
61
  * Returns properties required to hook into form events.
43
62
  * Applied custom validation and define when error should be reported.
44
63
  *
45
- * @see https://github.com/edmundhung/conform/tree/v0.3.0/packages/conform-react/README.md#useform
64
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#useform
46
65
  */
47
- export declare function useForm(config?: FormConfig): FormProps;
66
+ export declare function useForm<Schema extends Record<string, any>>(config?: FormConfig<Schema>): Form<Schema>;
48
67
  /**
49
68
  * All the information of the field, including state and config.
50
69
  */
@@ -70,7 +89,7 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {
70
89
  /**
71
90
  * An object describing the initial error of each field
72
91
  */
73
- initialError?: FieldError<Schema>['details'];
92
+ initialError?: Array<[string, string]>;
74
93
  /**
75
94
  * An object describing the constraint of each field
76
95
  */
@@ -83,7 +102,7 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {
83
102
  /**
84
103
  * Returns all the information about the fieldset.
85
104
  *
86
- * @see https://github.com/edmundhung/conform/tree/v0.3.0/packages/conform-react/README.md#usefieldset
105
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usefieldset
87
106
  */
88
107
  export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config?: FieldsetConfig<Schema>): Fieldset<Schema>;
89
108
  export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config?: FieldConfig<Schema>): Fieldset<Schema>;
@@ -110,7 +129,7 @@ interface ListControl<Schema> {
110
129
  * Returns a list of key and config, with a group of helpers
111
130
  * configuring buttons for list manipulation
112
131
  *
113
- * @see https://github.com/edmundhung/conform/tree/v0.3.0/packages/conform-react/README.md#usefieldlist
132
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usefieldlist
114
133
  */
115
134
  export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): [
116
135
  Array<{
@@ -122,7 +141,10 @@ export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormEleme
122
141
  interface ShadowInputProps extends InputHTMLAttributes<HTMLInputElement> {
123
142
  ref: RefObject<HTMLInputElement>;
124
143
  }
125
- interface InputControl {
144
+ interface InputControl<Element extends {
145
+ focus: () => void;
146
+ }> {
147
+ ref: RefObject<Element>;
126
148
  value: string;
127
149
  onChange: (eventOrValue: {
128
150
  target: {
@@ -137,7 +159,9 @@ interface InputControl {
137
159
  * This is particular useful when integrating dropdown and datepicker whichs
138
160
  * introduces custom input mode.
139
161
  *
140
- * @see https://github.com/edmundhung/conform/tree/v0.3.0/packages/conform-react/README.md#usecontrolledinput
162
+ * @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usecontrolledinput
141
163
  */
142
- export declare function useControlledInput<Schema extends Primitive = Primitive>(field: FieldConfig<Schema>): [ShadowInputProps, InputControl];
164
+ export declare function useControlledInput<Element extends {
165
+ focus: () => void;
166
+ } = HTMLInputElement, Schema extends Primitive = Primitive>(config: FieldConfig<Schema>): [ShadowInputProps, InputControl<Element>];
143
167
  export {};