From bce9b2c084abb5dc1ce0793809d820b38676a61d Mon Sep 17 00:00:00 2001
From: MVaik <105720462+MVaik@users.noreply.github.com>
Date: Sun, 16 Mar 2025 20:56:06 +0200
Subject: [PATCH 1/2] perf(react-form): skip updating isValidating by default
BREAKING CHANGE: isValidating is now opt-in
---
packages/form-core/src/FieldApi.ts | 18 ++++-
packages/react-form/tests/useField.test.tsx | 90 +++++++++++++++++----
2 files changed, 88 insertions(+), 20 deletions(-)
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..96e53e107 100644
--- a/packages/react-form/tests/useField.test.tsx
+++ b/packages/react-form/tests/useField.test.tsx
@@ -290,28 +290,26 @@ describe('useField', () => {
)
}
-
- return (
-
- {({ handleChange, state }) => (
-
- )}
-
- )
+ return null
}}
+
+ {({ handleChange, state }) => (
+
+ )}
+
>
)
}
- const { getByTestId } = render()
+ const { getByTestId, debug } = render()
const showFirstFieldInput = getByTestId('show-first-field')
@@ -1130,4 +1128,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)
+ })
})
From 1765bff20630d604b2387ea356b4c66f1032b409 Mon Sep 17 00:00:00 2001
From: MVaik <105720462+MVaik@users.noreply.github.com>
Date: Sun, 16 Mar 2025 21:20:26 +0200
Subject: [PATCH 2/2] chore(react-form): revert unprompted test "fix"
---
packages/react-form/tests/useField.test.tsx | 29 +++++++++++----------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/packages/react-form/tests/useField.test.tsx b/packages/react-form/tests/useField.test.tsx
index 96e53e107..015652e2b 100644
--- a/packages/react-form/tests/useField.test.tsx
+++ b/packages/react-form/tests/useField.test.tsx
@@ -290,26 +290,27 @@ describe('useField', () => {
)
}
- return null
+ return (
+
+ {({ handleChange, state }) => (
+
+ )}
+
+ )
}}
-
- {({ handleChange, state }) => (
-
- )}
-
>
)
}
- const { getByTestId, debug } = render()
+ const { getByTestId } = render()
const showFirstFieldInput = getByTestId('show-first-field')