Skip to content

Commit 3b7f6c1

Browse files
joaom00João Pedro Magalhães
andauthored
fix(form-core): remove 500ms fallback for asyncDebounceMs
* test(form-core): add async validation tests * chore(form-core): remove old asyncDebounceMs api * Revert "chore(form-core): remove old asyncDebounceMs api" This reverts commit cd7dbc1. * fix(form-core): make sure to fallback to asyncDebounceMs * chore(form-core): remove 500ms fallback --------- Co-authored-by: João Pedro Magalhães <[email protected]>
1 parent de22b6c commit 3b7f6c1

File tree

3 files changed

+252
-15
lines changed

3 files changed

+252
-15
lines changed

packages/form-core/src/FieldApi.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,6 @@ export class FieldApi<TData, TFormData> {
165165
update = (opts: FieldApiOptions<typeof this._tdata, TFormData>) => {
166166
this.options = {
167167
asyncDebounceMs: this.form.options.asyncDebounceMs ?? 0,
168-
onChangeAsyncDebounceMs: this.form.options.onChangeAsyncDebounceMs ?? 0,
169-
onBlurAsyncDebounceMs: this.form.options.onBlurAsyncDebounceMs ?? 0,
170168
...opts,
171169
} as never
172170

@@ -312,7 +310,7 @@ export class FieldApi<TData, TFormData> {
312310
? onChangeAsyncDebounceMs
313311
: onBlurAsyncDebounceMs) ??
314312
asyncDebounceMs ??
315-
500
313+
0
316314

317315
if (this.state.meta.isValidating !== true)
318316
this.setMeta((prev) => ({ ...prev, isValidating: true }))

packages/form-core/src/tests/FieldApi.spec.ts

Lines changed: 246 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { expect } from 'vitest'
22

33
import { FormApi } from '../FormApi'
44
import { FieldApi } from '../FieldApi'
5+
import { sleep } from './utils'
56

67
describe('field api', () => {
78
it('should have an initial value', () => {
@@ -151,7 +152,30 @@ describe('field api', () => {
151152
expect(subfield.getValue()).toBe('one')
152153
})
153154

154-
it('should run validation onChange', async () => {
155+
it('should not throw errors when no meta info is stored on a field and a form re-renders', async () => {
156+
const form = new FormApi({
157+
defaultValues: {
158+
name: 'test',
159+
},
160+
})
161+
162+
const field = new FieldApi({
163+
form,
164+
name: 'name',
165+
})
166+
167+
field.mount()
168+
169+
expect(() =>
170+
form.update({
171+
defaultValues: {
172+
name: 'other',
173+
},
174+
}),
175+
).not.toThrow()
176+
})
177+
178+
it('should run validation onChange', () => {
155179
const form = new FormApi({
156180
defaultValues: {
157181
name: 'test',
@@ -162,10 +186,33 @@ describe('field api', () => {
162186
form,
163187
name: 'name',
164188
onChange: (value) => {
165-
if (value === 'other') {
166-
return 'Please enter a different value'
167-
}
189+
if (value === 'other') return 'Please enter a different value'
190+
return
191+
},
192+
})
193+
194+
field.mount()
195+
196+
expect(field.getMeta().error).toBeUndefined()
197+
field.setValue('other', { touch: true })
198+
expect(field.getMeta().error).toBe('Please enter a different value')
199+
})
200+
201+
it('should run async validation onChange', async () => {
202+
vi.useFakeTimers()
168203

204+
const form = new FormApi({
205+
defaultValues: {
206+
name: 'test',
207+
},
208+
})
209+
210+
const field = new FieldApi({
211+
form,
212+
name: 'name',
213+
onChangeAsync: async (value) => {
214+
await sleep(1000)
215+
if (value === 'other') return 'Please enter a different value'
169216
return
170217
},
171218
})
@@ -174,10 +221,14 @@ describe('field api', () => {
174221

175222
expect(field.getMeta().error).toBeUndefined()
176223
field.setValue('other', { touch: true })
224+
await vi.runAllTimersAsync()
177225
expect(field.getMeta().error).toBe('Please enter a different value')
178226
})
179227

180-
it('should not throw errors when no meta info is stored on a field and a form re-renders', async () => {
228+
it('should run async validation onChange with debounce', async () => {
229+
vi.useFakeTimers()
230+
const sleepMock = vi.fn().mockImplementation(sleep)
231+
181232
const form = new FormApi({
182233
defaultValues: {
183234
name: 'test',
@@ -187,16 +238,199 @@ describe('field api', () => {
187238
const field = new FieldApi({
188239
form,
189240
name: 'name',
241+
onChangeAsyncDebounceMs: 1000,
242+
onChangeAsync: async (value) => {
243+
await sleepMock(1000)
244+
if (value === 'other') return 'Please enter a different value'
245+
return
246+
},
190247
})
191248

192249
field.mount()
193250

194-
expect(() =>
195-
form.update({
196-
defaultValues: {
197-
name: 'other',
198-
},
199-
}),
200-
).not.toThrow()
251+
expect(field.getMeta().error).toBeUndefined()
252+
field.setValue('other', { touch: true })
253+
field.setValue('other')
254+
await vi.runAllTimersAsync()
255+
// sleepMock will have been called 2 times without onChangeAsyncDebounceMs
256+
expect(sleepMock).toHaveBeenCalledTimes(1)
257+
expect(field.getMeta().error).toBe('Please enter a different value')
258+
})
259+
260+
it('should run async validation onChange with asyncDebounceMs', async () => {
261+
vi.useFakeTimers()
262+
const sleepMock = vi.fn().mockImplementation(sleep)
263+
264+
const form = new FormApi({
265+
defaultValues: {
266+
name: 'test',
267+
},
268+
})
269+
270+
const field = new FieldApi({
271+
form,
272+
name: 'name',
273+
asyncDebounceMs: 1000,
274+
onChangeAsync: async (value) => {
275+
await sleepMock(1000)
276+
if (value === 'other') return 'Please enter a different value'
277+
return
278+
},
279+
})
280+
281+
field.mount()
282+
283+
expect(field.getMeta().error).toBeUndefined()
284+
field.setValue('other', { touch: true })
285+
field.setValue('other')
286+
await vi.runAllTimersAsync()
287+
// sleepMock will have been called 2 times without asyncDebounceMs
288+
expect(sleepMock).toHaveBeenCalledTimes(1)
289+
expect(field.getMeta().error).toBe('Please enter a different value')
290+
})
291+
292+
it('should run validation onBlur', () => {
293+
const form = new FormApi({
294+
defaultValues: {
295+
name: 'other',
296+
},
297+
})
298+
299+
const field = new FieldApi({
300+
form,
301+
name: 'name',
302+
onBlur: (value) => {
303+
if (value === 'other') return 'Please enter a different value'
304+
return
305+
},
306+
})
307+
308+
field.mount()
309+
310+
field.setValue('other', { touch: true })
311+
field.validate('blur')
312+
expect(field.getMeta().error).toBe('Please enter a different value')
313+
})
314+
315+
it('should run async validation onBlur', async () => {
316+
vi.useFakeTimers()
317+
318+
const form = new FormApi({
319+
defaultValues: {
320+
name: 'test',
321+
},
322+
})
323+
324+
const field = new FieldApi({
325+
form,
326+
name: 'name',
327+
onBlurAsync: async (value) => {
328+
await sleep(1000)
329+
if (value === 'other') return 'Please enter a different value'
330+
return
331+
},
332+
})
333+
334+
field.mount()
335+
336+
expect(field.getMeta().error).toBeUndefined()
337+
field.setValue('other', { touch: true })
338+
field.validate('blur')
339+
await vi.runAllTimersAsync()
340+
expect(field.getMeta().error).toBe('Please enter a different value')
341+
})
342+
343+
it('should run async validation onBlur with debounce', async () => {
344+
vi.useFakeTimers()
345+
const sleepMock = vi.fn().mockImplementation(sleep)
346+
347+
const form = new FormApi({
348+
defaultValues: {
349+
name: 'test',
350+
},
351+
})
352+
353+
const field = new FieldApi({
354+
form,
355+
name: 'name',
356+
onBlurAsyncDebounceMs: 1000,
357+
onBlurAsync: async (value) => {
358+
await sleepMock(10)
359+
if (value === 'other') return 'Please enter a different value'
360+
return
361+
},
362+
})
363+
364+
field.mount()
365+
366+
expect(field.getMeta().error).toBeUndefined()
367+
field.setValue('other', { touch: true })
368+
field.validate('blur')
369+
field.validate('blur')
370+
await vi.runAllTimersAsync()
371+
// sleepMock will have been called 2 times without onBlurAsyncDebounceMs
372+
expect(sleepMock).toHaveBeenCalledTimes(1)
373+
expect(field.getMeta().error).toBe('Please enter a different value')
374+
})
375+
376+
it('should run async validation onBlur with asyncDebounceMs', async () => {
377+
vi.useFakeTimers()
378+
const sleepMock = vi.fn().mockImplementation(sleep)
379+
380+
const form = new FormApi({
381+
defaultValues: {
382+
name: 'test',
383+
},
384+
})
385+
386+
const field = new FieldApi({
387+
form,
388+
name: 'name',
389+
asyncDebounceMs: 1000,
390+
onBlurAsync: async (value) => {
391+
await sleepMock(10)
392+
if (value === 'other') return 'Please enter a different value'
393+
return
394+
},
395+
})
396+
397+
field.mount()
398+
399+
expect(field.getMeta().error).toBeUndefined()
400+
field.setValue('other', { touch: true })
401+
field.validate('blur')
402+
field.validate('blur')
403+
await vi.runAllTimersAsync()
404+
// sleepMock will have been called 2 times without asyncDebounceMs
405+
expect(sleepMock).toHaveBeenCalledTimes(1)
406+
expect(field.getMeta().error).toBe('Please enter a different value')
407+
})
408+
409+
it('should run async validation onSubmit', async () => {
410+
vi.useFakeTimers()
411+
412+
const form = new FormApi({
413+
defaultValues: {
414+
name: 'test',
415+
},
416+
})
417+
418+
const field = new FieldApi({
419+
form,
420+
name: 'name',
421+
onSubmitAsync: async (value) => {
422+
await sleep(1000)
423+
if (value === 'other') return 'Please enter a different value'
424+
return
425+
},
426+
})
427+
428+
field.mount()
429+
430+
expect(field.getMeta().error).toBeUndefined()
431+
field.setValue('other', { touch: true })
432+
field.validate('submit')
433+
await vi.runAllTimersAsync()
434+
expect(field.getMeta().error).toBe('Please enter a different value')
201435
})
202436
})

packages/form-core/src/tests/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function sleep(timeout: number): Promise<void> {
2+
return new Promise((resolve, _reject) => {
3+
setTimeout(resolve, timeout)
4+
})
5+
}

0 commit comments

Comments
 (0)