@tanstack/react-form 0.23.1 → 0.23.3

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,942 +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
- import type { FieldApi, FormApi } from '../index'
8
-
9
- const user = userEvent.setup()
10
-
11
- describe('useField', () => {
12
- it('should allow to set default value', () => {
13
- type Person = {
14
- firstName: string
15
- lastName: string
16
- }
17
-
18
- function Comp() {
19
- const form = useForm({
20
- defaultValues: {
21
- firstName: 'FirstName',
22
- lastName: 'LastName',
23
- } as Person,
24
- })
25
-
26
- return (
27
- <>
28
- <form.Field
29
- name="firstName"
30
- children={(field) => {
31
- return (
32
- <input
33
- data-testid="fieldinput"
34
- value={field.state.value}
35
- onBlur={field.handleBlur}
36
- onChange={(e) => field.handleChange(e.target.value)}
37
- />
38
- )
39
- }}
40
- />
41
- </>
42
- )
43
- }
44
-
45
- const { getByTestId } = render(<Comp />)
46
- const input = getByTestId('fieldinput')
47
- expect(input).toHaveValue('FirstName')
48
- })
49
-
50
- it('should use field default value first', async () => {
51
- type Person = {
52
- firstName: string
53
- lastName: string
54
- }
55
-
56
- function Comp() {
57
- const form = useForm({
58
- defaultValues: {
59
- firstName: 'FirstName',
60
- lastName: 'LastName',
61
- } as Person,
62
- })
63
-
64
- return (
65
- <>
66
- <form.Field
67
- name="firstName"
68
- defaultValue="otherName"
69
- children={(field) => {
70
- return (
71
- <input
72
- data-testid="fieldinput"
73
- value={field.state.value}
74
- onBlur={field.handleBlur}
75
- onChange={(e) => field.handleChange(e.target.value)}
76
- />
77
- )
78
- }}
79
- />
80
- </>
81
- )
82
- }
83
-
84
- const { getByTestId } = render(<Comp />)
85
- const input = getByTestId('fieldinput')
86
- expect(input).toHaveValue('otherName')
87
- })
88
-
89
- it('should not validate on change if isTouched is false', async () => {
90
- type Person = {
91
- firstName: string
92
- lastName: string
93
- }
94
- const error = 'Please enter a different value'
95
-
96
- function Comp() {
97
- const form = useForm({
98
- defaultValues: {
99
- firstName: '',
100
- lastName: '',
101
- } as Person,
102
- })
103
-
104
- return (
105
- <>
106
- <form.Field
107
- name="firstName"
108
- validators={{
109
- onChange: ({ value }) => (value === 'other' ? error : undefined),
110
- }}
111
- children={(field) => (
112
- <div>
113
- <input
114
- data-testid="fieldinput"
115
- name={field.name}
116
- value={field.state.value}
117
- onBlur={field.handleBlur}
118
- onChange={(e) => field.setValue(e.target.value)}
119
- />
120
- <p>{field.getMeta().errors}</p>
121
- </div>
122
- )}
123
- />
124
- </>
125
- )
126
- }
127
-
128
- const { getByTestId, queryByText } = render(<Comp />)
129
- const input = getByTestId('fieldinput')
130
- await user.type(input, 'other')
131
- expect(queryByText(error)).not.toBeInTheDocument()
132
- })
133
-
134
- it('should validate on change if isTouched is true', async () => {
135
- type Person = {
136
- firstName: string
137
- lastName: string
138
- }
139
- const error = 'Please enter a different value'
140
-
141
- function Comp() {
142
- const form = useForm({
143
- defaultValues: {
144
- firstName: '',
145
- lastName: '',
146
- } as Person,
147
- })
148
-
149
- return (
150
- <>
151
- <form.Field
152
- name="firstName"
153
- defaultMeta={{ isTouched: true }}
154
- validators={{
155
- onChange: ({ value }) => (value === 'other' ? error : undefined),
156
- }}
157
- children={(field) => (
158
- <div>
159
- <input
160
- data-testid="fieldinput"
161
- name={field.name}
162
- value={field.state.value}
163
- onBlur={field.handleBlur}
164
- onChange={(e) => field.handleChange(e.target.value)}
165
- />
166
- <p>{field.getMeta().errorMap?.onChange}</p>
167
- </div>
168
- )}
169
- />
170
- </>
171
- )
172
- }
173
-
174
- const { getByTestId, getByText, queryByText } = render(<Comp />)
175
- const input = getByTestId('fieldinput')
176
- expect(queryByText(error)).not.toBeInTheDocument()
177
- await user.type(input, 'other')
178
- expect(getByText(error)).toBeInTheDocument()
179
- })
180
-
181
- it('should validate on change and on blur', async () => {
182
- type Person = {
183
- firstName: string
184
- lastName: string
185
- }
186
- const onChangeError = 'Please enter a different value (onChangeError)'
187
- const onBlurError = 'Please enter a different value (onBlurError)'
188
-
189
- function Comp() {
190
- const form = useForm({
191
- defaultValues: {
192
- firstName: '',
193
- lastName: '',
194
- } as Person,
195
- })
196
-
197
- return (
198
- <>
199
- <form.Field
200
- name="firstName"
201
- defaultMeta={{ isTouched: true }}
202
- validators={{
203
- onChange: ({ value }) =>
204
- value === 'other' ? onChangeError : undefined,
205
- onBlur: ({ value }) =>
206
- value === 'other' ? onBlurError : undefined,
207
- }}
208
- children={(field) => (
209
- <div>
210
- <input
211
- data-testid="fieldinput"
212
- name={field.name}
213
- value={field.state.value}
214
- onBlur={field.handleBlur}
215
- onChange={(e) => field.handleChange(e.target.value)}
216
- />
217
- <p>{field.getMeta().errorMap?.onChange}</p>
218
- <p>{field.getMeta().errorMap?.onBlur}</p>
219
- </div>
220
- )}
221
- />
222
- </>
223
- )
224
- }
225
-
226
- const { getByTestId, getByText, queryByText } = render(<Comp />)
227
- const input = getByTestId('fieldinput')
228
- expect(queryByText(onChangeError)).not.toBeInTheDocument()
229
- expect(queryByText(onBlurError)).not.toBeInTheDocument()
230
- await user.type(input, 'other')
231
- expect(getByText(onChangeError)).toBeInTheDocument()
232
- await user.click(document.body)
233
- expect(queryByText(onBlurError)).toBeInTheDocument()
234
- })
235
-
236
- it('should validate async on change', async () => {
237
- type Person = {
238
- firstName: string
239
- lastName: string
240
- }
241
- const error = 'Please enter a different value'
242
-
243
- function Comp() {
244
- const form = useForm({
245
- defaultValues: {
246
- firstName: '',
247
- lastName: '',
248
- } as Person,
249
- })
250
-
251
- return (
252
- <>
253
- <form.Field
254
- name="firstName"
255
- defaultMeta={{ isTouched: true }}
256
- validators={{
257
- onChangeAsync: async () => {
258
- await sleep(10)
259
- return error
260
- },
261
- }}
262
- children={(field) => (
263
- <div>
264
- <input
265
- data-testid="fieldinput"
266
- name={field.name}
267
- value={field.state.value}
268
- onBlur={field.handleBlur}
269
- onChange={(e) => field.handleChange(e.target.value)}
270
- />
271
- <p>{field.getMeta().errorMap?.onChange}</p>
272
- </div>
273
- )}
274
- />
275
- </>
276
- )
277
- }
278
-
279
- const { getByTestId, getByText, queryByText } = render(<Comp />)
280
- const input = getByTestId('fieldinput')
281
- expect(queryByText(error)).not.toBeInTheDocument()
282
- await user.type(input, 'other')
283
- await waitFor(() => getByText(error))
284
- expect(getByText(error)).toBeInTheDocument()
285
- })
286
-
287
- it('should validate async on change and async on blur', async () => {
288
- type Person = {
289
- firstName: string
290
- lastName: string
291
- }
292
- const onChangeError = 'Please enter a different value (onChangeError)'
293
- const onBlurError = 'Please enter a different value (onBlurError)'
294
-
295
- function Comp() {
296
- const form = useForm({
297
- defaultValues: {
298
- firstName: '',
299
- lastName: '',
300
- } as Person,
301
- })
302
-
303
- return (
304
- <>
305
- <form.Field
306
- name="firstName"
307
- defaultMeta={{ isTouched: true }}
308
- validators={{
309
- onChangeAsync: async () => {
310
- await sleep(10)
311
- return onChangeError
312
- },
313
- onBlurAsync: async () => {
314
- await sleep(10)
315
- return onBlurError
316
- },
317
- }}
318
- children={(field) => (
319
- <div>
320
- <input
321
- data-testid="fieldinput"
322
- name={field.name}
323
- value={field.state.value}
324
- onBlur={field.handleBlur}
325
- onChange={(e) => field.handleChange(e.target.value)}
326
- />
327
- <p>{field.getMeta().errorMap?.onChange}</p>
328
- <p>{field.getMeta().errorMap?.onBlur}</p>
329
- </div>
330
- )}
331
- />
332
- </>
333
- )
334
- }
335
-
336
- const { getByTestId, getByText, queryByText } = render(<Comp />)
337
- const input = getByTestId('fieldinput')
338
-
339
- expect(queryByText(onChangeError)).not.toBeInTheDocument()
340
- expect(queryByText(onBlurError)).not.toBeInTheDocument()
341
- await user.type(input, 'other')
342
- await waitFor(() => getByText(onChangeError))
343
- expect(getByText(onChangeError)).toBeInTheDocument()
344
- await user.click(document.body)
345
- await waitFor(() => getByText(onBlurError))
346
- expect(getByText(onBlurError)).toBeInTheDocument()
347
- })
348
-
349
- it('should validate async on change with debounce', async () => {
350
- type Person = {
351
- firstName: string
352
- lastName: string
353
- }
354
- const mockFn = vi.fn()
355
- const error = 'Please enter a different value'
356
-
357
- function Comp() {
358
- const form = useForm({
359
- defaultValues: {
360
- firstName: '',
361
- lastName: '',
362
- } as Person,
363
- })
364
-
365
- return (
366
- <>
367
- <form.Field
368
- name="firstName"
369
- defaultMeta={{ isTouched: true }}
370
- validators={{
371
- onChangeAsyncDebounceMs: 100,
372
- onChangeAsync: async () => {
373
- mockFn()
374
- await sleep(10)
375
- return error
376
- },
377
- }}
378
- children={(field) => (
379
- <div>
380
- <input
381
- data-testid="fieldinput"
382
- name={field.name}
383
- value={field.state.value}
384
- onBlur={field.handleBlur}
385
- onChange={(e) => field.handleChange(e.target.value)}
386
- />
387
- <p>{field.getMeta().errors}</p>
388
- </div>
389
- )}
390
- />
391
- </>
392
- )
393
- }
394
-
395
- const { getByTestId, getByText } = render(<Comp />)
396
- const input = getByTestId('fieldinput')
397
- await user.type(input, 'other')
398
- // mockFn will have been called 5 times without onChangeAsyncDebounceMs
399
- expect(mockFn).toHaveBeenCalledTimes(0)
400
- await waitFor(() => getByText(error))
401
- expect(getByText(error)).toBeInTheDocument()
402
- })
403
-
404
- it('should preserve value when preserve value property is true', async () => {
405
- type Person = {
406
- firstName: string
407
- lastName: string
408
- }
409
- let form: FormApi<Person> | null = null
410
- function Comp() {
411
- form = useForm({
412
- defaultValues: {
413
- firstName: '',
414
- lastName: '',
415
- } as Person,
416
- })
417
- return (
418
- <>
419
- <form.Field
420
- name="firstName"
421
- defaultValue="hello"
422
- preserveValue={true}
423
- children={(field) => {
424
- return (
425
- <input
426
- data-testid="fieldinput"
427
- value={field.state.value}
428
- onBlur={field.handleBlur}
429
- onChange={(e) => field.handleChange(e.target.value)}
430
- />
431
- )
432
- }}
433
- />
434
- </>
435
- )
436
- }
437
-
438
- const { getByTestId, unmount, rerender } = render(<Comp />)
439
- const input = getByTestId('fieldinput')
440
- expect(input).toHaveValue('hello')
441
- await user.type(input, 'world')
442
- unmount()
443
- expect(form!.fieldInfo['firstName']).toBeDefined()
444
- })
445
-
446
- it('should not preserve value when preserve value property is false', async () => {
447
- type Person = {
448
- firstName: string
449
- lastName: string
450
- }
451
- let form: FormApi<Person> | null = null
452
- function Comp() {
453
- form = useForm({
454
- defaultValues: {
455
- firstName: '',
456
- lastName: '',
457
- } as Person,
458
- })
459
- return (
460
- <>
461
- <form.Field
462
- name="firstName"
463
- defaultValue="hello"
464
- preserveValue={false}
465
- children={(field) => {
466
- return (
467
- <input
468
- data-testid="fieldinput"
469
- value={field.state.value}
470
- onBlur={field.handleBlur}
471
- onChange={(e) => field.handleChange(e.target.value)}
472
- />
473
- )
474
- }}
475
- />
476
- </>
477
- )
478
- }
479
-
480
- const { getByTestId, unmount } = render(<Comp />)
481
- const input = getByTestId('fieldinput')
482
- expect(input).toHaveValue('hello')
483
- unmount()
484
- const info = form!.fieldInfo
485
- expect(Object.keys(info)).toHaveLength(0)
486
- })
487
-
488
- it('should handle strict mode properly with conditional fields', async () => {
489
- function FieldInfo({ field }: { field: FieldApi<any, any> }) {
490
- return (
491
- <>
492
- {field.state.meta.touchedErrors ? (
493
- <em>{field.state.meta.touchedErrors}</em>
494
- ) : null}
495
- {field.state.meta.isValidating ? 'Validating...' : null}
496
- </>
497
- )
498
- }
499
-
500
- function Comp() {
501
- const [showField, setShowField] = React.useState(true)
502
-
503
- const form = useForm({
504
- defaultValues: {
505
- firstName: '',
506
- lastName: '',
507
- },
508
- // eslint-disable-next-line @typescript-eslint/no-empty-function
509
- onSubmit: async () => {},
510
- })
511
-
512
- return (
513
- <div>
514
- <form
515
- onSubmit={(e) => {
516
- e.preventDefault()
517
- e.stopPropagation()
518
- form.handleSubmit()
519
- }}
520
- >
521
- <div>
522
- {/* A type-safe field component*/}
523
- {showField ? (
524
- <form.Field
525
- name="firstName"
526
- validators={{
527
- onChange: ({ value }) =>
528
- !value ? 'A first name is required' : undefined,
529
- }}
530
- children={(field) => {
531
- // Avoid hasty abstractions. Render props are great!
532
- return (
533
- <>
534
- <label htmlFor={field.name}>First Name:</label>
535
- <input
536
- name={field.name}
537
- value={field.state.value}
538
- onBlur={field.handleBlur}
539
- onChange={(e) => field.handleChange(e.target.value)}
540
- />
541
- <FieldInfo field={field} />
542
- </>
543
- )
544
- }}
545
- />
546
- ) : null}
547
- </div>
548
- <div>
549
- <form.Field
550
- name="lastName"
551
- children={(field) => (
552
- <>
553
- <label htmlFor={field.name}>Last Name:</label>
554
- <input
555
- name={field.name}
556
- value={field.state.value}
557
- onBlur={field.handleBlur}
558
- onChange={(e) => field.handleChange(e.target.value)}
559
- />
560
- <FieldInfo field={field} />
561
- </>
562
- )}
563
- />
564
- </div>
565
- <form.Subscribe
566
- selector={(state) => [state.canSubmit, state.isSubmitting]}
567
- children={([canSubmit, isSubmitting]) => (
568
- <button type="submit" disabled={!canSubmit}>
569
- {isSubmitting ? '...' : 'Submit'}
570
- </button>
571
- )}
572
- />
573
- <button type="button" onClick={() => setShowField((prev) => !prev)}>
574
- {showField ? 'Hide field' : 'Show field'}
575
- </button>
576
- </form>
577
- </div>
578
- )
579
- }
580
-
581
- const { getByText, findByText, queryByText } = render(
582
- <React.StrictMode>
583
- <Comp />
584
- </React.StrictMode>,
585
- )
586
-
587
- await user.click(getByText('Submit'))
588
- expect(await findByText('A first name is required')).toBeInTheDocument()
589
- await user.click(getByText('Hide field'))
590
- await user.click(getByText('Submit'))
591
- expect(queryByText('A first name is required')).not.toBeInTheDocument()
592
- })
593
-
594
- it('should handle arrays with primitive values', async () => {
595
- const fn = vi.fn()
596
- function Comp() {
597
- const form = useForm({
598
- defaultValues: {
599
- people: [] as Array<string>,
600
- },
601
- onSubmit: ({ value }) => fn(value),
602
- })
603
-
604
- return (
605
- <div>
606
- <form
607
- onSubmit={(e) => {
608
- e.preventDefault()
609
- e.stopPropagation()
610
- form.handleSubmit()
611
- }}
612
- >
613
- <form.Field name="people">
614
- {(field) => {
615
- return (
616
- <div>
617
- {field.state.value.map((_, i) => {
618
- return (
619
- <form.Field key={i} name={`people[${i}]`}>
620
- {(subField) => {
621
- return (
622
- <div>
623
- <label>
624
- <div>Name for person {i}</div>
625
- <input
626
- value={subField.state.value}
627
- onChange={(e) =>
628
- subField.handleChange(e.target.value)
629
- }
630
- />
631
- </label>
632
- <button
633
- onClick={() => field.removeValue(i)}
634
- type="button"
635
- >
636
- Remove person {i}
637
- </button>
638
- </div>
639
- )
640
- }}
641
- </form.Field>
642
- )
643
- })}
644
- <button onClick={() => field.pushValue('')} type="button">
645
- Add person
646
- </button>
647
- </div>
648
- )
649
- }}
650
- </form.Field>
651
- <form.Subscribe
652
- selector={(state) => [state.canSubmit, state.isSubmitting]}
653
- children={([canSubmit, isSubmitting]) => (
654
- <button type="submit" disabled={!canSubmit}>
655
- {isSubmitting ? '...' : 'Submit'}
656
- </button>
657
- )}
658
- />
659
- </form>
660
- </div>
661
- )
662
- }
663
-
664
- const { getByText, findByLabelText, queryByText, findByText } = render(
665
- <Comp />,
666
- )
667
-
668
- expect(queryByText('Name for person 0')).not.toBeInTheDocument()
669
- expect(queryByText('Name for person 1')).not.toBeInTheDocument()
670
- await user.click(getByText('Add person'))
671
- const input = await findByLabelText('Name for person 0')
672
- expect(input).toBeInTheDocument()
673
- await user.type(input, 'John')
674
-
675
- await user.click(getByText('Add person'))
676
- const input2 = await findByLabelText('Name for person 1')
677
- expect(input).toBeInTheDocument()
678
- await user.type(input2, 'Jack')
679
-
680
- expect(queryByText('Name for person 0')).toBeInTheDocument()
681
- expect(queryByText('Name for person 1')).toBeInTheDocument()
682
- await user.click(getByText('Remove person 1'))
683
- expect(queryByText('Name for person 0')).toBeInTheDocument()
684
- expect(queryByText('Name for person 1')).not.toBeInTheDocument()
685
-
686
- await user.click(await findByText('Submit'))
687
- expect(fn).toHaveBeenCalledWith({ people: ['John'] })
688
- })
689
-
690
- it('should handle arrays with subvalues', async () => {
691
- const fn = vi.fn()
692
- function Comp() {
693
- const form = useForm({
694
- defaultValues: {
695
- people: [] as Array<{ age: number; name: string }>,
696
- },
697
- onSubmit: ({ value }) => fn(value),
698
- })
699
-
700
- return (
701
- <div>
702
- <form
703
- onSubmit={(e) => {
704
- e.preventDefault()
705
- e.stopPropagation()
706
- form.handleSubmit()
707
- }}
708
- >
709
- <form.Field name="people">
710
- {(field) => {
711
- return (
712
- <div>
713
- {field.state.value.map((_, i) => {
714
- return (
715
- <form.Field key={i} name={`people[${i}].name`}>
716
- {(subField) => {
717
- return (
718
- <div>
719
- <label>
720
- <div>Name for person {i}</div>
721
- <input
722
- value={subField.state.value}
723
- onChange={(e) =>
724
- subField.handleChange(e.target.value)
725
- }
726
- />
727
- </label>
728
- <button
729
- onClick={() => field.removeValue(i)}
730
- type="button"
731
- >
732
- Remove person {i}
733
- </button>
734
- </div>
735
- )
736
- }}
737
- </form.Field>
738
- )
739
- })}
740
- <button
741
- onClick={() => field.pushValue({ name: '', age: 0 })}
742
- type="button"
743
- >
744
- Add person
745
- </button>
746
- </div>
747
- )
748
- }}
749
- </form.Field>
750
- <form.Subscribe
751
- selector={(state) => [state.canSubmit, state.isSubmitting]}
752
- children={([canSubmit, isSubmitting]) => (
753
- <button type="submit" disabled={!canSubmit}>
754
- {isSubmitting ? '...' : 'Submit'}
755
- </button>
756
- )}
757
- />
758
- </form>
759
- </div>
760
- )
761
- }
762
-
763
- const { getByText, findByLabelText, queryByText, findByText } = render(
764
- <Comp />,
765
- )
766
-
767
- expect(queryByText('Name for person 0')).not.toBeInTheDocument()
768
- expect(queryByText('Name for person 1')).not.toBeInTheDocument()
769
- await user.click(getByText('Add person'))
770
- const input = await findByLabelText('Name for person 0')
771
- expect(input).toBeInTheDocument()
772
- await user.type(input, 'John')
773
-
774
- await user.click(getByText('Add person'))
775
- const input2 = await findByLabelText('Name for person 1')
776
- expect(input).toBeInTheDocument()
777
- await user.type(input2, 'Jack')
778
-
779
- expect(queryByText('Name for person 0')).toBeInTheDocument()
780
- expect(queryByText('Name for person 1')).toBeInTheDocument()
781
- await user.click(getByText('Remove person 1'))
782
- expect(queryByText('Name for person 0')).toBeInTheDocument()
783
- expect(queryByText('Name for person 1')).not.toBeInTheDocument()
784
-
785
- await user.click(await findByText('Submit'))
786
- expect(fn).toHaveBeenCalledWith({ people: [{ name: 'John', age: 0 }] })
787
- })
788
-
789
- it('should handle sync linked fields', async () => {
790
- const fn = vi.fn()
791
- function Comp() {
792
- const form = useForm({
793
- defaultValues: {
794
- password: '',
795
- confirm_password: '',
796
- },
797
- onSubmit: ({ value }) => fn(value),
798
- })
799
-
800
- return (
801
- <div>
802
- <form.Field name="password">
803
- {(field) => {
804
- return (
805
- <div>
806
- <label>
807
- <div>Password</div>
808
- <input
809
- value={field.state.value}
810
- onChange={(e) => field.handleChange(e.target.value)}
811
- />
812
- </label>
813
- </div>
814
- )
815
- }}
816
- </form.Field>
817
- <form.Field
818
- name="confirm_password"
819
- validators={{
820
- onChangeListenTo: ['password'],
821
- onChange: ({ value, fieldApi }) => {
822
- if (value !== fieldApi.form.getFieldValue('password')) {
823
- return 'Passwords do not match'
824
- }
825
- return undefined
826
- },
827
- }}
828
- >
829
- {(field) => {
830
- return (
831
- <div>
832
- <label>
833
- <div>Confirm Password</div>
834
- <input
835
- value={field.state.value}
836
- onChange={(e) => field.handleChange(e.target.value)}
837
- />
838
- </label>
839
- {field.state.meta.errors.map((err) => {
840
- return <div key={err?.toString()}>{err}</div>
841
- })}
842
- </div>
843
- )
844
- }}
845
- </form.Field>
846
- </div>
847
- )
848
- }
849
-
850
- const { findByLabelText, queryByText, findByText } = render(<Comp />)
851
-
852
- const passwordInput = await findByLabelText('Password')
853
- const confirmPasswordInput = await findByLabelText('Confirm Password')
854
- await user.type(passwordInput, 'password')
855
- await user.type(confirmPasswordInput, 'password')
856
- expect(queryByText('Passwords do not match')).not.toBeInTheDocument()
857
- await user.type(confirmPasswordInput, '1')
858
- expect(await findByText('Passwords do not match')).toBeInTheDocument()
859
- })
860
-
861
- it('should handle deeply nested values in StrictMode', async () => {
862
- function Comp() {
863
- const form = useForm({
864
- defaultValues: {
865
- name: { first: 'Test', last: 'User' },
866
- },
867
- })
868
-
869
- return (
870
- <form.Field
871
- name="name.last"
872
- children={(field) => <p>{field.state.value ?? ''}</p>}
873
- />
874
- )
875
- }
876
-
877
- const { queryByText, findByText } = render(
878
- <React.StrictMode>
879
- <Comp />
880
- </React.StrictMode>,
881
- )
882
-
883
- expect(queryByText('Test')).not.toBeInTheDocument()
884
- expect(await findByText('User')).toBeInTheDocument()
885
- })
886
-
887
- it('should validate async on submit without debounce', async () => {
888
- type Person = {
889
- firstName: string
890
- lastName: string
891
- }
892
- const mockFn = vi.fn()
893
- const error = 'Please enter a different value'
894
-
895
- function Comp() {
896
- const form = useForm({
897
- defaultValues: {
898
- firstName: '',
899
- lastName: '',
900
- } as Person,
901
- validators: {
902
- onChangeAsyncDebounceMs: 1000000,
903
- onChangeAsync: async () => {
904
- mockFn()
905
- await sleep(10)
906
- return error
907
- },
908
- },
909
- })
910
- const errors = form.useStore((s) => s.errors)
911
-
912
- return (
913
- <>
914
- <form.Field
915
- name="firstName"
916
- defaultMeta={{ isTouched: true }}
917
- children={(field) => (
918
- <div>
919
- <input
920
- data-testid="fieldinput"
921
- name={field.name}
922
- value={field.state.value}
923
- onBlur={field.handleBlur}
924
- onChange={(e) => field.handleChange(e.target.value)}
925
- />
926
- <p>{errors}</p>
927
- </div>
928
- )}
929
- />
930
- <button onClick={form.handleSubmit}>Submit</button>
931
- </>
932
- )
933
- }
934
-
935
- const { getByRole, getByText } = render(<Comp />)
936
- await user.click(getByRole('button', { name: 'Submit' }))
937
-
938
- expect(mockFn).toHaveBeenCalledTimes(1)
939
- await waitFor(() => getByText(error))
940
- expect(getByText(error)).toBeInTheDocument()
941
- })
942
- })