Every React project used to get its own hand-rolled form validation logic, and every time we found a new edge case in one project's version, the other five never got the fix, which is exactly the kind of quiet, compounding inconsistency that accumulates across a growing portfolio of client projects when nobody owns a shared solution. This year we standardized on Formik for form state management and Yup for schema-based validation, used together.
Formik handles the tedious parts of form state — touched fields, submission state, error display timing — that we used to reimplement slightly differently every time, and the differences were never intentional design decisions, just small variations in how each project's original developer happened to solve the same recurring problem under their own specific deadline pressure. Yup's schema-based approach lets validation rules live as a single declarative object rather than scattered conditional checks across a component, and the same schema can, in principle, validate on the server too if the backend is also JavaScript, which we have started doing on at least one project to avoid maintaining two separate, potentially drifting sets of validation rules.
What this actually changed day to day
It is a small productivity change but a real one — new forms across our React projects now take a fraction of the time they used to, and they are more consistent in how they behave, which matters as much for the end user experience as it does for our own development speed. A user filling out a form on one of our client's sites now sees the same error-display timing and the same general interaction pattern regardless of which project or which developer happened to build that particular form, which was never reliably true before this standardization.
We have started building a small internal library of common Yup validation schemas — email format, phone number format, common address field patterns — specifically to avoid even the standardized version of this problem quietly re-fragmenting across projects the same way the original hand-rolled validation logic did, on the theory that a shared library needs active maintenance and reuse to actually stay shared rather than just being a good idea that individual project teams quietly drift away from under their own deadline pressure.
Making error messages genuinely accessible, not just visible
A validation error that only shows up as a color change or a small icon next to a field is a real accessibility gap, and it is one our old hand-rolled implementations handled inconsistently across projects at best. Standardizing on Formik gave us a single place to also standardize on announcing validation errors to screen readers via `aria-live` regions and associating each error message with its field through `aria-describedby`, a pattern we now bake into the shared component library itself rather than leaving it to each developer to remember on every single form they build.
Where schema reuse between client and server actually paid off
On the one project where we are sharing a single Yup schema between a Node backend and a React frontend, the payoff has been catching a mismatch we would not have caught otherwise: a field the frontend treated as optional that the backend's separate, independently written validation logic actually required, discovered immediately once both sides pointed at the same schema definition rather than two versions someone had to remember to keep in sync by hand. We do not have this luxury on projects with a backend in a different language, where the two validation layers necessarily stay separate, but even there, standardizing the frontend half onto Yup has made the frontend side of that inherent duplication easier to review and keep intentional.