diff --git a/packages/form-core/src/FieldApi.ts b/packages/form-core/src/FieldApi.ts index 3013e7aaa..c201f80c9 100644 --- a/packages/form-core/src/FieldApi.ts +++ b/packages/form-core/src/FieldApi.ts @@ -439,6 +439,10 @@ export interface FieldOptions< * Disable the `flat(1)` operation on `field.errors`. This is useful if you want to keep the error structure as is. Not suggested for most use-cases. */ disableErrorFlat?: boolean + /** + * Should the field's isValidating state be updated during validation + */ + trackValidationState?: boolean } /** @@ -1473,12 +1477,14 @@ export class FieldApi< >, ) - if (!this.state.meta.isValidating) { + if (!this.state.meta.isValidating && this.options.trackValidationState) { this.setMeta((prev) => ({ ...prev, isValidating: true })) } for (const linkedField of linkedFields) { - linkedField.setMeta((prev) => ({ ...prev, isValidating: true })) + if (linkedField.options.trackValidationState) { + linkedField.setMeta((prev) => ({ ...prev, isValidating: true })) + } } /** @@ -1577,10 +1583,14 @@ export class FieldApi< await Promise.all(linkedPromises) } - this.setMeta((prev) => ({ ...prev, isValidating: false })) + if (this.options.trackValidationState) { + this.setMeta((prev) => ({ ...prev, isValidating: false })) + } for (const linkedField of linkedFields) { - linkedField.setMeta((prev) => ({ ...prev, isValidating: false })) + if (linkedField.options.trackValidationState) { + linkedField.setMeta((prev) => ({ ...prev, isValidating: false })) + } } return results.filter(Boolean) diff --git a/packages/react-form/tests/useField.test.tsx b/packages/react-form/tests/useField.test.tsx index a5969c4e8..015652e2b 100644 --- a/packages/react-form/tests/useField.test.tsx +++ b/packages/react-form/tests/useField.test.tsx @@ -290,7 +290,6 @@ describe('useField', () => { ) } - return ( {({ handleChange, state }) => ( @@ -1130,4 +1129,64 @@ describe('useField', () => { // field2 should not have rerendered expect(renderCount.field2).toBe(field2InitialRender) }) + + it('should render once per change if not tracking validation state', async () => { + let regularRenders = 0 + let rendersWithTracking = 0 + const inputText = 'example' + const expectedRenders = inputText.length + // 1 by default and seems like 1 for useStore + const baseRenders = 2 + + function Comp() { + const form = useForm({ + defaultValues: { + field1: '', + field2: '', + }, + }) + + return ( + <> + { + regularRenders++ + return ( + field.handleChange(e.target.value)} + /> + ) + }} + /> + { + rendersWithTracking++ + return ( + field.handleChange(e.target.value)} + /> + ) + }} + /> + + ) + } + + const { getByTestId } = render() + await user.type(getByTestId('fieldinput1'), inputText) + expect(regularRenders).toEqual(expectedRenders + baseRenders) + + await user.type(getByTestId('fieldinput2'), inputText) + // Each change triggers two renders due to isValidating state being updated + expect(rendersWithTracking).toEqual(expectedRenders * 2 + baseRenders) + }) })