react-simple-formkit 1.3.6 → 2.0.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
@@ -1,10 +1,33 @@
1
1
  # React Simple FormKit
2
2
 
3
- Configuration is simple, fast, and efficient. There are also many other tools available for different special scenarios.
4
-
5
- # Examples
6
-
7
- - https://codesandbox.io/p/sandbox/react-simple-formkit-examples-rhmhjj
3
+ Support manage forms as uncontrolled. State updates only when watched. Simple and quick to configure with outstanding efficiency.
4
+
5
+ # Table of Contents
6
+
7
+ - [Peer dependencies](#peer-dependencies)
8
+ - [Features](#features)
9
+ - [Quick start](#quick-start)
10
+ - [Core concepts](#core-concepts)
11
+ - [Modes of input field](#modes-of-input-field)
12
+ - [Watching for updates](#watching-for-updates)
13
+ - [Manage values](#manage-values)
14
+ - [Get value](#get-value)
15
+ - [Set value](#set-value)
16
+ - [Default values and reset](#default-values-and-reset)
17
+ - [Manage states](#manage-states)
18
+ - [Get field states](#get-field-states)
19
+ - [Set field states](#set-field-states)
20
+ - [Manage errors](#manage-errors)
21
+ - [Get field error](#get-field-error)
22
+ - [Set field error](#set-field-error)
23
+ - [APIs](#apis)
24
+ - [useForm](#useForm)
25
+ - [Form](#form)
26
+ - [Controller](#controller)
27
+ - [useController](#useController)
28
+ - [useWatch](#useWatch)
29
+ - [Examples](#examples)
30
+ - [Contact](#contact)
8
31
 
9
32
  # Peer dependencies
10
33
 
@@ -15,161 +38,166 @@ Configuration is simple, fast, and efficient. There are also many other tools av
15
38
  },
16
39
  ```
17
40
 
41
+ # Features
42
+
43
+ - [Manage values](#manage-values) (get value, set value, default values, reset values,...)
44
+ - [Manage states](#manage-states) (field dirty, field touched, custom states...)
45
+ - [Manage errors](#manage-errors) (form error, field error...)
46
+
18
47
  # Quick start
19
48
 
20
49
  ```
21
- import { Form, useForm } form 'react-simple-formkit'
22
-
23
- const form = useForm();
50
+ const { control } = useForm();
24
51
 
25
52
  const handleSubmit = (data) => {
26
53
  alert(JSON.stringify(data));
27
54
  };
28
55
 
29
56
  return (
30
- <Form form={form} onSubmit={handleSubmit}>
57
+ <Form control={control} onSubmit={handleSubmit} onChange={console.log}>
58
+ <button type="submit">Submit</button>
31
59
  <input required name="email" placeholder="email" />
32
60
  <input required name="password" type="password" placeholder="password" />
33
- <input required name="input1" placeholder="input 1" />
34
- <input required name="input2" placeholder="input 2" />
35
- {/* ... */}
36
- <input required name="input100" placeholder="input 100" />
37
- <button type="submit">Submit</button>
61
+ {Array.from({ length: 10 }).map((_, index) => (
62
+ <input name={`input${index + 1}`} placeholder={`input ${index + 1}`} />
63
+ ))}
38
64
  </Form>
39
65
  );
40
66
  ```
41
67
 
42
- # More kits
68
+ # Core concepts
43
69
 
44
- ## Number fields
70
+ ## Modes of input field
45
71
 
46
- - By default form, values are string at all. So, adding numberFields will catch the number value in on submit
72
+ - `Uncontrolled`: Basic input types are supported by the browser and no need to control value
73
+ - `Controlled`: Advanced input types (multiple select, date picker,...) that **require controlled value** or just need to control value
74
+ - `Captured`: Advanced input types **but no need to control value**. Capture only because the browser doesn't support it
47
75
 
48
76
  ```
49
- const form = useForm( {numberFields: ['number'] } );
77
+ const { control, actions } = useForm();
50
78
 
51
79
  return (
52
- <Form form={form}>
53
- <input name="number" placeholder=This is number field" />
54
- <button type="submit">
55
- Submit
56
- </button>
57
- </Form>
58
- );
80
+ <Form control={control} onChange={console.log}>
81
+ {/* Uncontrolled */}
82
+ <input name="number1" placeholder="Enter a number" />
83
+ {/* Controlled by Controller. And the value at this field can be controlled by actions.setValue() */}
84
+ <Controller
85
+ name="multipleSelect"
86
+ render={({ value = "", onChange, name }) => {
87
+ return (
88
+ <Select
89
+ multiple
90
+ name={name}
91
+ value={value.split(",")}
92
+ onChange={(e) => {
93
+ const value = e.target.value;
94
+ // value as array by default but on autofill value as string
95
+ onChange(typeof value === "string" ? value : value.join(","));
96
+ }}
97
+ >
98
+ <MenuItem value="10">Ten</MenuItem>
99
+ <MenuItem value="20">Twenty</MenuItem>
100
+ <MenuItem value="30">Thirty</MenuItem>
101
+ </Select>
102
+ );
103
+ }}
104
+ />
105
+ {/* Captured: This datepicker component changes value immediately with a click with custom onChange */}
106
+ <LocalizationProvider dateAdapter={AdapterDayjs}>
107
+ <DatePicker name="date" onChange={(newValue) => actions.setValue("date", newValue.format("MM/DD/YYYY"))} />
108
+ </LocalizationProvider>
109
+ <button type="button" onClick={() => alert(JSON.stringify(actions.getValues()))}>
110
+ Get value
111
+ </button>
112
+ <button type="submit">Submit</button>
113
+ </Form>
114
+ )
59
115
  ```
60
116
 
61
- ## JS Built-in validate on blur
117
+ - **Noticed:** <span style="color:red">_All fields in the form must follow one of the three modes above to ensure the form works correctly_</span>
62
118
 
63
- ```
64
- const form = useForm();
65
- const { isDirty, isError, errors, actions } = form;
119
+ ## Watching for updates
66
120
 
67
- return (
68
- <Form form={form}>
69
- <input required name="email" placeholder="email" type="email" onBlur={actions.checkValidity} />
70
- {errors.email && <span>{errors.email}</span>}
71
-
72
- <button type="submit" disabled={!isDirty || isError}>
73
- Submit
74
- </button>
75
- </Form>
76
- );
77
- ```
121
+ State updates only when observed via `watch()` or `useWatch()`, or by tracking changes through the Form component's `onChange` callback.
78
122
 
79
- ## Double validate on blur
123
+ - `watch()` will trigger a re-render at the form level.
124
+ - `useWatch()` will trigger a re-render only in the component where it is called.
125
+ - `onChange` is just a handler callback that is called when field values change.
80
126
 
81
- ```
82
- const form = useForm();
83
- const { isDirty, isError, errors, actions } = form;
127
+ # Manage values
128
+
129
+ ## Get value
84
130
 
85
- return (
86
- <Form form={form}>
87
- <input
88
- required
89
- name="number"
90
- onBlur={(e) => {
91
- let error = null;
92
- error = actions.getFieldValidity(e);
93
-
94
- if (error) {
95
- actions.changeError("number", error);
96
- return;
97
- }
98
-
99
- const value = Number(e.target.value || 0);
100
- if (value >= 10) error = "Number must be less than 10";
101
- actions.changeError("number", error);
102
- }}
103
- />
104
- {errors.number && <span className="error-message">{errors.number}</span>}
105
-
106
- <button type="submit" disabled={!isDirty || isError}>
107
- Submit
108
- </button>
109
- </Form>
110
- );
111
131
  ```
132
+ // watch
133
+ const fieldName = watch("fieldName")
134
+ const fieldName2 = watch("fieldName2")
135
+ // Or
136
+ const { fieldName2, fieldName } = watch(["fieldName", "fieldName2"])
137
+ // Or
138
+ const values = watch("values")
139
+
140
+ // useWatch
141
+ useWatch({ name: "fieldName" })
142
+ useWatch({ name: ["fieldName", "fieldName2"] })
143
+ useWatch({ compute: (values) => values.number > 10 : true : false })
144
+
145
+ // actions.getValues()
146
+ const { actions } = useForm()
147
+ console.log(actions.getValues())
148
+ ```
149
+
150
+ ## Set value
112
151
 
113
- ## Validate by other field
152
+ actions.setValue() can help to control value of a field. But that field must be controlled by Controller.
114
153
 
115
154
  ```
116
- const form = useForm();
117
- const { isDirty, isError, errors, actions } = form;
155
+ const { control, actions, watch } = useForm()
156
+ const number = watch("number")
118
157
 
119
158
  return (
120
- <Form form={form}>
121
- <input required name="number" onBlur={actions.checkValidity} />
122
- {errors.number && <span className="error-message">{errors.number}</span>}
123
-
124
- <input
125
- required
126
- name="number2"
127
- onBlur={(e) => {
128
- let error = null;
129
- error = actions.getFieldValidity(e);
130
-
131
- if (error) {
132
- actions.changeError("number2", error);
133
- return;
134
- }
135
-
136
- const value = Number(e.target.value || 0);
137
- const formState = actions.getFormState();
138
- const number = Number(formState.number || 0);
139
-
140
- if (value >= number) error = "Number 2 must be less than number 1";
141
- actions.changeError("number2", error);
142
- }}
159
+ <Form control={control} onChange={console.log}>
160
+ <Controller
161
+ name="number"
162
+ render={({ name, value, onChange }) => (
163
+ <input name={name} value={value} onChange={(e) => onChange(e.target.value)} />
164
+ )}
143
165
  />
144
- {errors.number2 && <span className="error-message">{errors.number2}</span>}
145
- <button type="submit" disabled={!isDirty || isError}>
166
+ <button type="button" onClick={() => actions.setValue("number", Number(number || 0) + 1 )}>
167
+ Increase
168
+ </button>
169
+ <button type="submit" disabled={!isDirty}>
146
170
  Submit
147
171
  </button>
148
172
  </Form>
149
173
  )
150
174
  ```
151
175
 
152
- ## Centralized processing with onChange
176
+ ## Default values and reset
153
177
 
154
178
  ```
155
- const handleChange = (data) => {
156
- console.log(data);
179
+ const dummyFields = Array.from({ length: 10 }).reduce(
180
+ (acc, _, index) => ({ ...acc, [`input${index + 1}`]: `input${index + 1}` }),
181
+ {}
182
+ );
157
183
 
158
- // setValue will trigger onChange by default and recall handle change making an infinite loop
159
- // That why we put shouldOnChange = false here
160
- actions.setValue("email2", data.email + "2", { shouldOnChange: false });
161
- };
184
+ const { control, watch, actions } = useForm({ defaultValues: dummyFields });
185
+
186
+ const handleSubmit = async (newValues) => {
187
+ // update to server
188
+ await new Promise(res => setTimeout(res, 1000))
189
+ // reset with new defaultValues
190
+ actions.reset(newValues)
191
+ }
162
192
 
163
193
  return (
164
- <Form form={form} onChange={handleChange}>
165
- <input required name="email" />
166
- {/* Controller make this field assignable */}
167
- <Controller
168
- name="email2"
169
- render={({ ref, name, value, setValue }) => (
170
- <input required ref={ref} name={name} value={value} onChange={(e) => setValue(e.target.value)} />
171
- )}
172
- />
194
+ <Form control={control} onSubmit={handleSubmit} onChange={console.log}>
195
+ {Object.keys(dummyFields).map((name) => (
196
+ <input name={name} placeholder={name} />
197
+ ))}
198
+ <button type="reset" disabled={!isDirty}>
199
+ Reset
200
+ </button>
173
201
  <button type="submit" disabled={!isDirty}>
174
202
  Submit
175
203
  </button>
@@ -177,79 +205,50 @@ return (
177
205
  );
178
206
  ```
179
207
 
180
- ## Default values
208
+ # Manage states
181
209
 
182
- - Just put defaultValue in input field, this library will load it when mounted and store to compare isDirty
183
- - You can also use actions.getDefaultValues() to check what is stored
210
+ ## Get field states
184
211
 
185
212
  ```
186
- const [loading, setLoading] = useState(false);
187
- const [defaultValues, setDefaultValues] = useState({ email: "email@example.com", password: "123456" });
188
-
189
- const form = useForm({ defaultValues });
190
- const { isDirty, actions } = form;
191
-
192
- const handleSubmit = async (data) => {
193
- setLoading(true);
194
- await new Promise((res) => setTimeout(res, 1500));
195
- setDefaultValues(data);
196
- // After update default values, reset the form to get new default values
197
- // Because data updating is asynchronous. Catching it's change is ineffective.
198
- actions.reset();
199
- setLoading(false);
200
- };
213
+ // watch() or useWatch()
214
+ const isFormDirty = watch("isDirty")
215
+ const { fieldName: {...}, fieldName2: {...} } = watch("fieldStates")
216
+ const { isDirty, isTouched } = watch("fieldStates.fieldName")
217
+ const isFieldDirty = watch("fieldStates.fieldName.isDirty")
218
+ const fieldCustomState = watch("fieldStates.fieldName.customState")
219
+ ```
220
+
221
+ ## Set field states
201
222
 
202
- return (
203
- <Form form={form} onSubmit={handleSubmit}>
204
- <input required name="email" />
205
- <Controller
206
- name="password"
207
- render={({ ref, name, defaultValue, value, setValue }) => (
208
- <input
209
- ref={ref}
210
- required
211
- name={name}
212
- type="password"
213
- defaultValue={defaultValue}
214
- value={value}
215
- onChange={(e) => setValue(e.target.value)}
216
- />
217
- )}
218
- />
219
- <button type="reset" disabled={!isDirty}>
220
- Reset
221
- </button>
222
- <Button disableElevation variant="contained" type="submit" loading={loading} disabled={!isDirty}>
223
- Submit
224
- </Button>
225
- </Form>
226
- );
227
223
  ```
224
+ actions.setFieldStateProperty('fieldName', 'customState', { hello: "world" })
225
+ // This will trigger re-render at watch(), useWatch() and render function in Controller
226
+ ```
227
+
228
+ # Manage errors
228
229
 
229
- # Support for third party library
230
+ ## Get field error
230
231
 
231
- ## Catch value instantly
232
+ ```
233
+ // watch() or useWatch()
234
+ const isFormError = watch("isError")
235
+ const fieldError = watch("errors.fieldName")
236
+ const { fieldName, fieldName2 } = watch("errors")
237
+ ```
232
238
 
233
- - Some input types like DatePicker, Select just change by click and don't dispatch input actions
234
- - You also catch this on submit, but cannot use for actions.getFormState() for validate and isDirty update
235
- - Just put the actions.instantChange where these components onchange
239
+ ## Set field error
236
240
 
237
241
  ```
238
- const form = useForm();
239
- const { isDirty, actions } = form;
242
+ // actions.setError()
243
+ const { control, actions, watch } = useForm()
244
+ const { isError, ["errors.input1"]: input1Error } = watch(["isError", "errors.input1"])
240
245
 
241
246
  return (
242
- <Form form={form}>
243
- <Select name="select" onChange={actions.instantChange}>
244
- <MenuItem value={10}>Ten</MenuItem>
245
- <MenuItem value={20}>Twenty</MenuItem>
246
- <MenuItem value={30}>Thirty</MenuItem>
247
- </Select>
248
- <LocalizationProvider dateAdapter={AdapterDayjs}>
249
- <DatePicker name="date" onChange={actions.instantChange} />
250
- </LocalizationProvider>
251
- <button type="button" onClick={() => alert(JSON.stringify(actions.getFormState()))}>
252
- Get value
247
+ <Form control={control} onChange={console.log}>
248
+ <input name="input1" />
249
+ {errors.input1 && <span className="error-message">{input1Error}</span>}
250
+ <button type="button" onClick={() => actions.setError("number", "This is error message")}>
251
+ Set Error
253
252
  </button>
254
253
  <button type="submit" disabled={!isDirty}>
255
254
  Submit
@@ -258,87 +257,77 @@ return (
258
257
  )
259
258
  ```
260
259
 
261
- ## More control with Controller
260
+ # APIs
262
261
 
263
- - Control custom values like object and array
264
- - Make field assignable by actions.setValue
262
+ ## useForm
265
263
 
266
- ```
267
- import { Controller, Form, useForm } from "react-simple-formkit";
264
+ Generic props:
268
265
 
269
- const form = useForm({ numberFields: ["number1", "number2"] });
270
- const { isDirty, actions } = form;
266
+ - `defaultValues`: `Object` [Example](#default-values-and-reset)
267
+ - `shouldUnRegister`: `Boolean` Default is **false**,
268
+ - `shouldConvertNumber`: `Boolean` Default is **false**
269
+ - `numberFields`: `Array` if passed, shouldConvertNumber will set **true**. Lib auto load field with type **number** by default
271
270
 
272
- return (
273
- <Form form={form}>
274
- {/* Custom value as array */}
275
- <Controller
276
- name="multipleSelect"
277
- defaultValue={[]}
278
- render={({ ref, value, setValue, name }) => (
279
- <Select
280
- multiple
281
- ref={ref}
282
- name={name}
283
- labelId="demo-multiple-name-label"
284
- id="demo-multiple-name"
285
- value={value}
286
- onChange={(e) => {
287
- const value = e.target.value;
288
- // On autofill we get a stringified value.
289
- setValue(typeof value === "string" ? value.split(",") : value);
290
- // instantChange by select
291
- actions.instantChange();
292
- }}
293
- input={<OutlinedInput label="Name" />}
294
- >
295
- <MenuItem value={10}>Ten</MenuItem>
296
- <MenuItem value={20}>Twenty</MenuItem>
297
- <MenuItem value={30}>Thirty</MenuItem>
298
- </Select>
299
- )}
300
- />
301
- {/* actions.setValue (target field of actions.setValue must use controller)*/}
302
- <input
303
- name="number1"
304
- placeholder="number 1"
305
- onBlur={(e) => {
306
- const number1 = Number(e.target.value || 0);
307
- const number2 = actions.getFormState().number2 || 0;
308
- actions.setValue("sum", number1 + number2);
309
- }}
310
- />
311
- <input
312
- name="number2"
313
- placeholder="number 2"
314
- onBlur={(e) => {
315
- const number2 = Number(e.target.value || 0);
316
- const number1 = actions.getFormState().number1 || 0;
317
- actions.setValue("sum", number1 + number2);
318
- }}
319
- />
320
- <Controller
321
- name="sum"
322
- render={({ ref, value, setValue, name }) => (
323
- <input
324
- ref={ref}
325
- value={value}
326
- onChange={(e) => setValue(e.target.value)}
327
- name={name}
328
- placeholder="sum(number1, number2)"
329
- />
330
- )}
331
- />
332
- <button type="button" onClick={() => alert(JSON.stringify(actions.getFormState()))}>
333
- Get value
334
- </button>
335
- <button type="submit" disabled={!isDirty}>
336
- Submit
337
- </button>
338
- </Form>
339
- )
340
- ```
271
+ Return:
272
+
273
+ - `control`: contains methods and utilities to control the form.
274
+ - `watch(name)`: `Function` [Example](#manage-values)
275
+ - `actions` is an object that contains utilities
276
+ - `actions.reset()`: [Example](#default-values-and-reset)
277
+ - `actions.getValues()`: [Example](#manage-values)
278
+ - `actions.setValue()`: [Example](#set-value)
279
+ - `actions.setError()`: [Example](#set-field-error)
280
+ - `actions.clearError()`
281
+ - `actions.clearErrors()`
282
+ - `actions.getNumberFields()`
283
+ - `actions.getDefaultValues()`
284
+ - `actions.setFieldStateProperty()`: [Example](#set-field-states)
285
+
286
+ ## Form
287
+
288
+ Generic props:
289
+
290
+ - `control`: received from useForm()
291
+ - `onSubmit`: `(currentValues) => {}` call when form submit by button type='submit'
292
+ - `onChange`: `(name, value, currentValues) => {}` call when any field changes
293
+
294
+ ## Controller
295
+
296
+ Generic props:
297
+
298
+ - `name`
299
+ - `defaultValue`
300
+ - `render({name, value, onChange, customState, setCustomState})`
301
+
302
+ ## useController
303
+
304
+ Generic props:
305
+
306
+ - `name`
307
+ - `defaultValue`
308
+
309
+ Return: everything in render function above
310
+
311
+ ## useWatch
312
+
313
+ [Example](#manage-values)
314
+
315
+ Generic props:
316
+
317
+ - `name`: `String | Array`
318
+ - compute: `Function` that will calculate from form values and return a value. It will make re-render when the result changes
319
+
320
+ ## useFormContext
321
+
322
+ Return:
323
+
324
+ - `watch`
325
+ - `actions`
326
+
327
+ # Examples
328
+
329
+ - https://codesandbox.io/p/sandbox/react-simple-formkit-examples-rhmhjj
341
330
 
342
331
  # Contact
343
332
 
344
- For any ideas or issues, please get in touch with me at anhhuy2000@gmail.com
333
+ For any ideas or issues, please get in touch with me at **anhhuy2000@gmail.com**