Skip to content

Commit 9a86503

Browse files
authored
fix: only allow setting manual errors according to the defined validators (#1482)
1 parent 5319847 commit 9a86503

File tree

7 files changed

+147
-46
lines changed

7 files changed

+147
-46
lines changed

examples/react/field-errors-from-form-validators/src/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,7 @@ export default function App() {
112112
errorMap.onSubmit ? (
113113
<div>
114114
<em>
115-
There was an error on the form:{' '}
116-
{errorMap.onSubmit?.toString()}
115+
There was an error on the form: {errorMap.onSubmit.toString()}
117116
</em>
118117
</div>
119118
) : null

packages/form-core/src/FieldApi.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,17 +1671,24 @@ export class FieldApi<
16711671
/**
16721672
* Updates the field's errorMap
16731673
*/
1674-
setErrorMap(errorMap: ValidationErrorMap) {
1675-
this.setMeta(
1676-
(prev) =>
1677-
({
1678-
...prev,
1679-
errorMap: {
1680-
...prev.errorMap,
1681-
...errorMap,
1682-
},
1683-
}) as never,
1684-
)
1674+
setErrorMap(
1675+
errorMap: ValidationErrorMap<
1676+
UnwrapFieldValidateOrFn<TName, TOnMount, TFormOnMount>,
1677+
UnwrapFieldValidateOrFn<TName, TOnChange, TFormOnChange>,
1678+
UnwrapFieldAsyncValidateOrFn<TName, TOnChangeAsync, TFormOnChangeAsync>,
1679+
UnwrapFieldValidateOrFn<TName, TOnBlur, TFormOnBlur>,
1680+
UnwrapFieldAsyncValidateOrFn<TName, TOnBlurAsync, TFormOnBlurAsync>,
1681+
UnwrapFieldValidateOrFn<TName, TOnSubmit, TFormOnSubmit>,
1682+
UnwrapFieldAsyncValidateOrFn<TName, TOnSubmitAsync, TFormOnSubmitAsync>
1683+
>,
1684+
) {
1685+
this.setMeta((prev) => ({
1686+
...prev,
1687+
errorMap: {
1688+
...prev.errorMap,
1689+
...errorMap,
1690+
},
1691+
}))
16851692
}
16861693

16871694
/**

packages/form-core/src/FormApi.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2123,25 +2123,23 @@ export class FormApi<
21232123
*/
21242124
setErrorMap(
21252125
errorMap: ValidationErrorMap<
2126-
TOnMount,
2127-
TOnChange,
2128-
TOnChangeAsync,
2129-
TOnBlur,
2130-
TOnBlurAsync,
2131-
TOnSubmit,
2132-
TOnSubmitAsync
2126+
UnwrapFormValidateOrFn<TOnMount>,
2127+
UnwrapFormValidateOrFn<TOnChange>,
2128+
UnwrapFormAsyncValidateOrFn<TOnChangeAsync>,
2129+
UnwrapFormValidateOrFn<TOnBlur>,
2130+
UnwrapFormAsyncValidateOrFn<TOnBlurAsync>,
2131+
UnwrapFormValidateOrFn<TOnSubmit>,
2132+
UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>,
2133+
UnwrapFormAsyncValidateOrFn<TOnServer>
21332134
>,
21342135
) {
2135-
this.baseStore.setState(
2136-
(prev) =>
2137-
({
2138-
...prev,
2139-
errorMap: {
2140-
...prev.errorMap,
2141-
...errorMap,
2142-
},
2143-
}) as never,
2144-
)
2136+
this.baseStore.setState((prev) => ({
2137+
...prev,
2138+
errorMap: {
2139+
...prev.errorMap,
2140+
...errorMap,
2141+
},
2142+
}))
21452143
}
21462144

21472145
/**

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

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,9 +1785,7 @@ describe('field api', () => {
17851785
name: 'name',
17861786
})
17871787
nameField.mount()
1788-
nameField.setErrorMap({
1789-
onChange: "name can't be Josh",
1790-
})
1788+
nameField.setErrorMap({ onChange: "name can't be Josh" as never })
17911789
expect(nameField.getMeta().isValid).toBe(false)
17921790
expect(nameField.getMeta().errorMap.onChange).toEqual("name can't be Josh")
17931791
})
@@ -1802,14 +1800,10 @@ describe('field api', () => {
18021800
name: 'name',
18031801
})
18041802
nameField.mount()
1805-
nameField.setErrorMap({
1806-
onChange: "name can't be Josh",
1807-
})
1803+
nameField.setErrorMap({ onChange: "name can't be Josh" as never })
18081804
expect(nameField.getMeta().isValid).toBe(false)
18091805
expect(nameField.getMeta().errorMap.onChange).toEqual("name can't be Josh")
1810-
nameField.setErrorMap({
1811-
onBlur: 'name must begin with uppercase',
1812-
})
1806+
nameField.setErrorMap({ onBlur: 'name must begin with uppercase' as never })
18131807
expect(nameField.getMeta().isValid).toBe(false)
18141808
expect(nameField.getMeta().errorMap.onChange).toEqual("name can't be Josh")
18151809
expect(nameField.getMeta().errorMap.onBlur).toEqual(
@@ -1827,14 +1821,10 @@ describe('field api', () => {
18271821
name: 'name',
18281822
})
18291823
nameField.mount()
1830-
nameField.setErrorMap({
1831-
onChange: "name can't be Josh",
1832-
})
1824+
nameField.setErrorMap({ onChange: "name can't be Josh" as never })
18331825
expect(nameField.getMeta().isValid).toBe(false)
18341826
expect(nameField.getMeta().errorMap.onChange).toEqual("name can't be Josh")
1835-
nameField.setErrorMap({
1836-
onChange: 'other validation error',
1837-
})
1827+
nameField.setErrorMap({ onChange: 'other validation error' as never })
18381828
expect(nameField.getMeta().errorMap.onChange).toEqual(
18391829
'other validation error',
18401830
)

packages/form-core/tests/FieldApi.test-d.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,3 +394,67 @@ it('should only have field-level error types returned from parseValueWithSchema
394394
Promise<StandardSchemaV1Issue[] | undefined>
395395
>()
396396
})
397+
398+
it("should allow setting manual errors according to the validator's return type", () => {
399+
const form = new FormApi({
400+
defaultValues: {
401+
firstName: '',
402+
lastName: '',
403+
},
404+
validators: {
405+
onChange: () => {
406+
return {
407+
fields: {
408+
firstName: '123' as const,
409+
},
410+
}
411+
},
412+
},
413+
})
414+
415+
const field = new FieldApi({
416+
form,
417+
name: 'firstName',
418+
validators: {
419+
onChange: () => 10 as const,
420+
onBlur: () => ['onBlur'] as const,
421+
},
422+
})
423+
424+
field.setErrorMap({
425+
onChange: '123',
426+
})
427+
428+
expectTypeOf(field.setErrorMap).parameter(0).toEqualTypeOf<{
429+
onMount: undefined
430+
onChange: '123' | 10 | undefined
431+
onBlur: readonly ['onBlur'] | undefined
432+
onSubmit: undefined
433+
onServer: unknown
434+
}>
435+
})
436+
437+
it('should allow setting manual errors with standard schema validators on the field level', () => {
438+
const form = new FormApi({
439+
defaultValues: {
440+
firstName: '',
441+
lastName: '',
442+
},
443+
})
444+
445+
const field = new FieldApi({
446+
form,
447+
name: 'firstName',
448+
validators: {
449+
onChange: z.string(),
450+
},
451+
})
452+
453+
expectTypeOf(field.setErrorMap).parameter(0).toEqualTypeOf<{
454+
onMount: undefined
455+
onChange: { message: string }[] | undefined
456+
onBlur: undefined
457+
onSubmit: undefined
458+
onServer: unknown
459+
}>
460+
})

packages/form-core/tests/FormApi.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,7 @@ describe('form api', () => {
736736
field2.mount()
737737

738738
field1.handleBlur()
739-
field1.setErrorMap({ onSubmit: 'test' })
739+
field1.setErrorMap({ onSubmit: 'test' as never })
740740

741741
expect(field0.state.meta.isBlurred).toBe(false)
742742
expect(field1.state.meta.isBlurred).toBe(true)

packages/form-core/tests/FormApi.test-d.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,46 @@ it('should only have form-level error types returned from parseFieldValuesWithSc
116116
Promise<FormLevelStandardSchemaIssue | undefined>
117117
>()
118118
})
119+
120+
it("should allow setting manual errors according to the validator's return type", () => {
121+
const form = new FormApi({
122+
defaultValues: {
123+
firstName: '',
124+
lastName: '',
125+
},
126+
validators: {
127+
onChange: () => ['onChange'] as const,
128+
onMount: () => 10 as const,
129+
onBlur: () => ({ onBlur: true as const, onBlurNumber: 1 }),
130+
onSubmit: () => 'onSubmit' as const,
131+
onBlurAsync: () => Promise.resolve('onBlurAsync' as const),
132+
onChangeAsync: () => Promise.resolve('onChangeAsync' as const),
133+
onSubmitAsync: () => Promise.resolve('onSubmitAsync' as const),
134+
},
135+
})
136+
137+
expectTypeOf(form.setErrorMap).parameter(0).toEqualTypeOf<{
138+
onMount: 10 | undefined
139+
onChange: readonly ['onChange'] | 'onChangeAsync' | undefined
140+
onBlur: { onBlur: true; onBlurNumber: number } | 'onBlurAsync' | undefined
141+
onSubmit: 'onSubmit' | 'onSubmitAsync' | undefined
142+
onServer: undefined
143+
}>
144+
})
145+
146+
it('should not allow setting manual errors if no validator is specified', () => {
147+
const form = new FormApi({
148+
defaultValues: {
149+
firstName: '',
150+
lastName: '',
151+
},
152+
})
153+
154+
expectTypeOf(form.setErrorMap).parameter(0).toEqualTypeOf<{
155+
onMount: undefined
156+
onChange: undefined
157+
onBlur: undefined
158+
onSubmit: undefined
159+
onServer: undefined
160+
}>
161+
})

0 commit comments

Comments
 (0)