Using zod schema as a form level onChange validator causes form elements to unnecessarily re-render.
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 6.7k
- Forks
- 682
- Avg merge
- 5d 18h
- Merged PRs (30d)
- 7
Description
Describe the bug
Let's consider the example from the documentation which uses a zod schema as its form-level onChange validator:
https://tanstack.com/form/latest/docs/framework/react/examples/standard-schema
const ZodSchema = z.object({
firstName: z
.string()
.min(3, '[Zod] You must have a length of at least 3')
.startsWith('A', "[Zod] First name must start with 'A'"),
lastName: z.string().min(3, '[Zod] You must have a length of at least 3'),
})
export default function App() {
const form = useForm({
validators: {
onChange: ZodSchema,
},
})
return (
///
)
}
https://github.com/user-attachments/assets/b42e6208-6650-4166-9b48-cb0e2ccf180d
Initially, typing in either of the inputs will cause both of them to re-render on each keystroke. This is because the onChange validator seemingly triggers on each keystroke, and each field which contains an error will be re-rendered. I speculate this is because the internal fieldMeta gets updated, and that's causing this issue. But as soon as any of the inputs no longer contains invalid data, it stops getting re-rendered.
That would be okay in some scenarios, but what if you want to trigger validation only on fields that have isDirty: true state, or in other words, fields that users have already interacted with. In most cases it makes no sense to validate and display errors if the user have provided no input yet.
Most importantly, this particular behavior isn't documented anywhere. I basically spent several hours trying to pinpoint the exact issue until I was able to figure out what is going on.
Here is where things get extra confusing. Let's try to mimic the zod validation by writing our own simple validator like this:
export default function App() {
const form = useForm({
validators: {
onChange: ({ value }) => {
return {
fields: {
...value.firstName.length < 3 && {
firstName: '[Custom] You must have a length of at least 3'
},
...!value.firstName.startsWith('A') && {
firstName: "[Custom] First name must start with 'A'"
},
...value.lastName.length < 3 && {
lastName: '[Custom] You must have a length of at least 3'
},
}
}
}
},
})
return (
//
)
}
Now the problem is gone. All we ever did was remove the nested message property from the error map and return the error as string instead. Why that works? I have no clue.
https://github.com/user-attachments/assets/6ee74678-43a1-4d39-8ed3-60380aa692cb
Your minimal, reproducible example
https://codesandbox.io/p/devbox/gallant-stitch-5sqhyv
Steps to reproduce
Reproduction examples shown above.
Expected behavior
Form-level validators onChange validators should not cause all elements to re-render, and/or there should be an option to configure the default behavior. Most importantly, this should be documented somewhere, and if it's the intended behavior then it should be listed as a limitation.
Currently on crossroads not knowing what to do and if I should go back to RHF because this is kind of a deal-breakers for my particular use case.
Cheers.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
- OS: Linux
- Browser: Firefox/Chromium
TanStack Form adapter
react-form
TanStack Form version
1.14.2
TypeScript version
No response
Additional context
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the React standard-schema documentation example and the linked CodeSandbox reproduction, comparing the Zod form-level onChange validator with the custom validator. Trace the form and React adapter update paths to identify why invalid field errors trigger extra renders; done means preventing unnecessary re-renders or documenting the intended limitation and validating the behavior with a regression test.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100