@tanstack/react-form 0.23.2 → 0.24.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.
@@ -1,36 +0,0 @@
1
- import * as React from 'react'
2
- import { assertType, it } from 'vitest'
3
- import { useForm } from '../useForm'
4
-
5
- it('should type onSubmit properly', () => {
6
- function Comp() {
7
- const form = useForm({
8
- defaultValues: {
9
- firstName: 'test',
10
- age: 84,
11
- // as const is required here
12
- } as const,
13
- onSubmit({ value }) {
14
- assertType<84>(value.age)
15
- },
16
- })
17
- }
18
- })
19
-
20
- it('should type a validator properly', () => {
21
- function Comp() {
22
- const form = useForm({
23
- defaultValues: {
24
- firstName: 'test',
25
- age: 84,
26
- // as const is required here
27
- } as const,
28
- validators: {
29
- onChange({ value }) {
30
- assertType<84>(value.age)
31
- return undefined
32
- },
33
- },
34
- })
35
- }
36
- })
@@ -1,524 +0,0 @@
1
- import * as React from 'react'
2
- import { describe, expect, it, vi } from 'vitest'
3
- import { render, waitFor } from '@testing-library/react'
4
- import userEvent from '@testing-library/user-event'
5
- import { useForm } from '../index'
6
- import { sleep } from './utils'
7
-
8
- const user = userEvent.setup()
9
-
10
- describe('useForm', () => {
11
- it('preserves field state', async () => {
12
- type Person = {
13
- firstName: string
14
- lastName: string
15
- }
16
-
17
- function Comp() {
18
- const form = useForm<Person>()
19
-
20
- return (
21
- <>
22
- <form.Field
23
- name="firstName"
24
- defaultValue={''}
25
- children={(field) => {
26
- return (
27
- <input
28
- data-testid="fieldinput"
29
- value={field.state.value}
30
- onBlur={field.handleBlur}
31
- onChange={(e) => field.handleChange(e.target.value)}
32
- />
33
- )
34
- }}
35
- />
36
- </>
37
- )
38
- }
39
-
40
- const { getByTestId, queryByText } = render(<Comp />)
41
- const input = getByTestId('fieldinput')
42
- expect(queryByText('FirstName')).not.toBeInTheDocument()
43
- await user.type(input, 'FirstName')
44
- expect(input).toHaveValue('FirstName')
45
- })
46
-
47
- it('should allow default values to be set', async () => {
48
- type Person = {
49
- firstName: string
50
- lastName: string
51
- }
52
-
53
- function Comp() {
54
- const form = useForm({
55
- defaultValues: {
56
- firstName: 'FirstName',
57
- lastName: 'LastName',
58
- } as Person,
59
- })
60
-
61
- return (
62
- <>
63
- <form.Field
64
- name="firstName"
65
- children={(field) => {
66
- return <p>{field.state.value}</p>
67
- }}
68
- />
69
- </>
70
- )
71
- }
72
-
73
- const { findByText, queryByText } = render(<Comp />)
74
- expect(await findByText('FirstName')).toBeInTheDocument()
75
- expect(queryByText('LastName')).not.toBeInTheDocument()
76
- })
77
-
78
- it('should handle submitting properly', async () => {
79
- function Comp() {
80
- const [submittedData, setSubmittedData] = React.useState<{
81
- firstName: string
82
- } | null>(null)
83
-
84
- const form = useForm({
85
- defaultValues: {
86
- firstName: 'FirstName',
87
- },
88
- onSubmit: ({ value }) => {
89
- setSubmittedData(value)
90
- },
91
- })
92
-
93
- return (
94
- <>
95
- <form.Field
96
- name="firstName"
97
- children={(field) => {
98
- return (
99
- <input
100
- value={field.state.value}
101
- onBlur={field.handleBlur}
102
- onChange={(e) => field.handleChange(e.target.value)}
103
- placeholder={'First name'}
104
- />
105
- )
106
- }}
107
- />
108
- <button onClick={form.handleSubmit}>Submit</button>
109
- {submittedData && <p>Submitted data: {submittedData.firstName}</p>}
110
- </>
111
- )
112
- }
113
-
114
- const { findByPlaceholderText, getByText } = render(<Comp />)
115
- const input = await findByPlaceholderText('First name')
116
- await user.clear(input)
117
- await user.type(input, 'OtherName')
118
- await user.click(getByText('Submit'))
119
- await waitFor(() =>
120
- expect(getByText('Submitted data: OtherName')).toBeInTheDocument(),
121
- )
122
- })
123
-
124
- it('should run on form mount', async () => {
125
- function Comp() {
126
- const [formMounted, setFormMounted] = React.useState(false)
127
- const [mountForm, setMountForm] = React.useState(false)
128
-
129
- const form = useForm({
130
- defaultValues: {
131
- firstName: 'FirstName',
132
- },
133
- validators: {
134
- onMount: () => {
135
- setFormMounted(true)
136
- return undefined
137
- },
138
- },
139
- })
140
-
141
- return (
142
- <>
143
- {mountForm ? (
144
- <>
145
- <h1>{formMounted ? 'Form mounted' : 'Not mounted'}</h1>
146
- </>
147
- ) : (
148
- <button onClick={() => setMountForm(true)}>Mount form</button>
149
- )}
150
- </>
151
- )
152
- }
153
-
154
- const { getByText } = render(<Comp />)
155
- await user.click(getByText('Mount form'))
156
- await waitFor(() => expect(getByText('Form mounted')).toBeInTheDocument())
157
- })
158
-
159
- it('should validate async on change for the form', async () => {
160
- type Person = {
161
- firstName: string
162
- lastName: string
163
- }
164
- const error = 'Please enter a different value'
165
-
166
- function Comp() {
167
- const form = useForm({
168
- defaultValues: {
169
- firstName: '',
170
- lastName: '',
171
- } as Person,
172
- validators: {
173
- onChange() {
174
- return error
175
- },
176
- },
177
- })
178
- const onChangeError = form.useStore((s) => s.errorMap.onChange)
179
- return (
180
- <>
181
- <form.Field
182
- name="firstName"
183
- children={(field) => (
184
- <input
185
- data-testid="fieldinput"
186
- name={field.name}
187
- value={field.state.value}
188
- onBlur={field.handleBlur}
189
- onChange={(e) => field.handleChange(e.target.value)}
190
- />
191
- )}
192
- />
193
- <p>{onChangeError}</p>
194
- </>
195
- )
196
- }
197
-
198
- const { getByTestId, getByText, queryByText } = render(<Comp />)
199
- const input = getByTestId('fieldinput')
200
- expect(queryByText(error)).not.toBeInTheDocument()
201
- await user.type(input, 'other')
202
- await waitFor(() => getByText(error))
203
- expect(getByText(error)).toBeInTheDocument()
204
- })
205
-
206
- it('should not validate on change if isTouched is false', async () => {
207
- type Person = {
208
- firstName: string
209
- lastName: string
210
- }
211
- const error = 'Please enter a different value'
212
-
213
- function Comp() {
214
- const form = useForm({
215
- defaultValues: {
216
- firstName: '',
217
- lastName: '',
218
- } as Person,
219
- validators: {
220
- onChange: ({ value }) =>
221
- value.firstName === 'other' ? error : undefined,
222
- },
223
- })
224
-
225
- const errors = form.useStore((s) => s.errors)
226
- return (
227
- <>
228
- <form.Field
229
- name="firstName"
230
- children={(field) => (
231
- <div>
232
- <input
233
- data-testid="fieldinput"
234
- name={field.name}
235
- value={field.state.value}
236
- onBlur={field.handleBlur}
237
- onChange={(e) => field.setValue(e.target.value)}
238
- />
239
- <p>{errors}</p>
240
- </div>
241
- )}
242
- />
243
- </>
244
- )
245
- }
246
-
247
- const { getByTestId, queryByText } = render(<Comp />)
248
- const input = getByTestId('fieldinput')
249
- await user.type(input, 'other')
250
- expect(queryByText(error)).not.toBeInTheDocument()
251
- })
252
-
253
- it('should validate on change if isTouched is true', async () => {
254
- type Person = {
255
- firstName: string
256
- lastName: string
257
- }
258
- const error = 'Please enter a different value'
259
-
260
- function Comp() {
261
- const form = useForm({
262
- defaultValues: {
263
- firstName: '',
264
- lastName: '',
265
- } as Person,
266
- validators: {
267
- onChange: ({ value }) =>
268
- value.firstName === 'other' ? error : undefined,
269
- },
270
- })
271
- const errors = form.useStore((s) => s.errorMap)
272
- return (
273
- <>
274
- <form.Field
275
- name="firstName"
276
- defaultMeta={{ isTouched: true }}
277
- children={(field) => (
278
- <div>
279
- <input
280
- data-testid="fieldinput"
281
- name={field.name}
282
- value={field.state.value}
283
- onBlur={field.handleBlur}
284
- onChange={(e) => field.handleChange(e.target.value)}
285
- />
286
- <p>{errors.onChange}</p>
287
- </div>
288
- )}
289
- />
290
- </>
291
- )
292
- }
293
-
294
- const { getByTestId, getByText, queryByText } = render(<Comp />)
295
- const input = getByTestId('fieldinput')
296
- expect(queryByText(error)).not.toBeInTheDocument()
297
- await user.type(input, 'other')
298
- expect(getByText(error)).toBeInTheDocument()
299
- })
300
-
301
- it('should validate on change and on blur', async () => {
302
- const onChangeError = 'Please enter a different value (onChangeError)'
303
- const onBlurError = 'Please enter a different value (onBlurError)'
304
-
305
- function Comp() {
306
- const form = useForm({
307
- defaultValues: {
308
- firstName: '',
309
- },
310
- validators: {
311
- onChange: ({ value }) => {
312
- if (value.firstName === 'other') return onChangeError
313
- return undefined
314
- },
315
- onBlur: ({ value }) => {
316
- if (value.firstName === 'other') return onBlurError
317
- return undefined
318
- },
319
- },
320
- })
321
-
322
- const errors = form.useStore((s) => s.errorMap)
323
- return (
324
- <>
325
- <form.Field
326
- name="firstName"
327
- defaultMeta={{ isTouched: true }}
328
- children={(field) => (
329
- <div>
330
- <input
331
- data-testid="fieldinput"
332
- name={field.name}
333
- value={field.state.value}
334
- onBlur={field.handleBlur}
335
- onChange={(e) => field.handleChange(e.target.value)}
336
- />
337
- <p>{errors.onChange}</p>
338
- <p>{errors.onBlur}</p>
339
- </div>
340
- )}
341
- />
342
- </>
343
- )
344
- }
345
- const { getByTestId, getByText, queryByText } = render(<Comp />)
346
- const input = getByTestId('fieldinput')
347
- expect(queryByText(onChangeError)).not.toBeInTheDocument()
348
- expect(queryByText(onBlurError)).not.toBeInTheDocument()
349
- await user.type(input, 'other')
350
- expect(getByText(onChangeError)).toBeInTheDocument()
351
- await user.click(document.body)
352
- expect(queryByText(onBlurError)).toBeInTheDocument()
353
- })
354
-
355
- it('should validate async on change', async () => {
356
- type Person = {
357
- firstName: string
358
- lastName: string
359
- }
360
- const error = 'Please enter a different value'
361
-
362
- function Comp() {
363
- const form = useForm({
364
- defaultValues: {
365
- firstName: '',
366
- lastName: '',
367
- } as Person,
368
- validators: {
369
- onChangeAsync: async () => {
370
- await sleep(10)
371
- return error
372
- },
373
- },
374
- })
375
- const errors = form.useStore((s) => s.errorMap)
376
- return (
377
- <>
378
- <form.Field
379
- name="firstName"
380
- defaultMeta={{ isTouched: true }}
381
- children={(field) => (
382
- <div>
383
- <input
384
- data-testid="fieldinput"
385
- name={field.name}
386
- value={field.state.value}
387
- onBlur={field.handleBlur}
388
- onChange={(e) => field.handleChange(e.target.value)}
389
- />
390
- <p>{errors.onChange}</p>
391
- </div>
392
- )}
393
- />
394
- </>
395
- )
396
- }
397
-
398
- const { getByTestId, getByText, queryByText } = render(<Comp />)
399
- const input = getByTestId('fieldinput')
400
- expect(queryByText(error)).not.toBeInTheDocument()
401
- await user.type(input, 'other')
402
- await waitFor(() => getByText(error))
403
- expect(getByText(error)).toBeInTheDocument()
404
- })
405
-
406
- it('should validate async on change and async on blur', async () => {
407
- type Person = {
408
- firstName: string
409
- lastName: string
410
- }
411
- const onChangeError = 'Please enter a different value (onChangeError)'
412
- const onBlurError = 'Please enter a different value (onBlurError)'
413
-
414
- function Comp() {
415
- const form = useForm({
416
- defaultValues: {
417
- firstName: '',
418
- lastName: '',
419
- } as Person,
420
- validators: {
421
- onChangeAsync: async () => {
422
- await sleep(10)
423
- return onChangeError
424
- },
425
- onBlurAsync: async () => {
426
- await sleep(10)
427
- return onBlurError
428
- },
429
- },
430
- })
431
- const errors = form.useStore((s) => s.errorMap)
432
-
433
- return (
434
- <>
435
- <form.Field
436
- name="firstName"
437
- defaultMeta={{ isTouched: true }}
438
- children={(field) => (
439
- <div>
440
- <input
441
- data-testid="fieldinput"
442
- name={field.name}
443
- value={field.state.value}
444
- onBlur={field.handleBlur}
445
- onChange={(e) => field.handleChange(e.target.value)}
446
- />
447
- <p>{errors.onChange}</p>
448
- <p>{errors.onBlur}</p>
449
- </div>
450
- )}
451
- />
452
- </>
453
- )
454
- }
455
-
456
- const { getByTestId, getByText, queryByText } = render(<Comp />)
457
- const input = getByTestId('fieldinput')
458
-
459
- expect(queryByText(onChangeError)).not.toBeInTheDocument()
460
- expect(queryByText(onBlurError)).not.toBeInTheDocument()
461
- await user.type(input, 'other')
462
- await waitFor(() => getByText(onChangeError))
463
- expect(getByText(onChangeError)).toBeInTheDocument()
464
- await user.click(document.body)
465
- await waitFor(() => getByText(onBlurError))
466
- expect(getByText(onBlurError)).toBeInTheDocument()
467
- })
468
-
469
- it('should validate async on change with debounce', async () => {
470
- type Person = {
471
- firstName: string
472
- lastName: string
473
- }
474
- const mockFn = vi.fn()
475
- const error = 'Please enter a different value'
476
-
477
- function Comp() {
478
- const form = useForm({
479
- defaultValues: {
480
- firstName: '',
481
- lastName: '',
482
- } as Person,
483
- validators: {
484
- onChangeAsyncDebounceMs: 100,
485
- onChangeAsync: async () => {
486
- mockFn()
487
- await sleep(10)
488
- return error
489
- },
490
- },
491
- })
492
- const errors = form.useStore((s) => s.errors)
493
-
494
- return (
495
- <>
496
- <form.Field
497
- name="firstName"
498
- defaultMeta={{ isTouched: true }}
499
- children={(field) => (
500
- <div>
501
- <input
502
- data-testid="fieldinput"
503
- name={field.name}
504
- value={field.state.value}
505
- onBlur={field.handleBlur}
506
- onChange={(e) => field.handleChange(e.target.value)}
507
- />
508
- <p>{errors}</p>
509
- </div>
510
- )}
511
- />
512
- </>
513
- )
514
- }
515
-
516
- const { getByTestId, getByText } = render(<Comp />)
517
- const input = getByTestId('fieldinput')
518
- await user.type(input, 'other')
519
- // mockFn will have been called 5 times without onChangeAsyncDebounceMs
520
- expect(mockFn).toHaveBeenCalledTimes(0)
521
- await waitFor(() => getByText(error))
522
- expect(getByText(error)).toBeInTheDocument()
523
- })
524
- })
@@ -1,5 +0,0 @@
1
- export function sleep(timeout: number): Promise<void> {
2
- return new Promise((resolve, _reject) => {
3
- setTimeout(resolve, timeout)
4
- })
5
- }