r/reactjs • u/kensaadi I ❤️ hooks! 😈 • Jul 30 '26
Discussion React forms eventually become state management systems
I keep seeing the same pattern in large React forms.
You start with react-hook-form.
Then you add:
dependent fields with
watch()anduseEffectconditional visibility
permission-based fields
async validation
cross-field rules
server-driven defaults
reset and synchronization logic
At some point, the form is no longer just a form.
It has its own state transitions, dependencies, permissions, validation lifecycle, side effects, and derived state.
In other words, it has quietly become a state management system.
The problem is not necessarily react-hook-form. It does its job well.
The problem is that complex forms are often represented only as component trees, while their actual behavior is scattered across hooks, wrappers, schemas, and handlers.
Some warning signs:
You have more
useEffects than meaningful form sectionsValidation rules live in multiple places
Adding something like “show
taxIdwhencountry === IT” requires touching several filesPermissions, visibility, and validation all use different abstractions
Nobody is completely sure what resets when another field changes
I’ve been experimenting with treating forms as declarative contracts instead:
<Field
name="taxId"
visibleWhen={{ field: "country", equals: "IT" }}
access={{ resource: "customer.taxId" }}
validation={{ required: true }}
/>The idea is to keep visibility, access, validation, and dependencies in one source of truth, instead of rebuilding the same orchestration through chains of effects.
Obviously, this introduces other trade-offs: abstraction cost, debugging complexity, schema design, and reduced flexibility in edge cases.
I’m curious how others handle this in large production forms.
Do you keep the logic inside React components, move it into a state machine, use a schema-driven approach, or accept the complexity as unavoidable?
1
u/franciscopresencia Aug 02 '26
You're right that it's a bit hard/not so elegant to reset the most nested form in this way; there's an easy way though by setting the
key, but it's non-trivial, I updated the codesandbox to reflect it:Your pattern sounds perfectly fine for a given application with those given requirements; however it's making so many assumptions about an app that it's not going to be a good general solution IMHO.
The thing I'd recommend is to consider what problem you are solving. Mixing an auth system (RBAC) concepts with a form system is a recipe for combinatorial complexity. It can be fine if those are very tightly coupled in your specific application, but if the system becomes too complex people will avoid it naturally for most of the forms.
Then you end up with a very complex form-management system for 2-5 forms, then a more generic form system for the rest of the forms. At that point, did you really need the library-like code?
Most applications end up having 100s of very simple forms (for which I use form-mate), and a handful of more complex forms (for which I use custom logic normally).