Skip to content

The Form Dilemma: Controlled vs. Uncontrolled

I often find myself caught in the debate over the "best" way to handle inputs in React.

3 min read
React Forms
Controlled Inputs
Uncontrolled Inputs
Validation
React Hook Form

I often find myself caught in the debate over the "best" way to handle inputs in React. Some swear by controlled components to keep everything in sync with the state, while others argue for uncontrolled components to keep performance snappy. In reality, there’s no absolute winner—both have their downsides.

Technically speaking, controlled means every single keystroke triggers a re-render because the React state is changing. Uncontrolled, on the other hand, lets the DOM hold the value, and you only grab the data when you actually need it, usually via ref or FormData.

It isn't about which one is "cooler"; it’s about what you actually need.

Controlled makes more sense when the UI needs to react instantly to what the user is typing. Think password strength indicators, character counters, or auto-formatting an input (like a phone number) as they type. If any other part of the screen depends on that input value, controlled is the way to go.

But if you're building a massive form with dozens of inputs, forcing a re-render on every single keystroke can become unnecessary overhead. This is where uncontrolled wins. If you only need to read the data once the submit button is clicked, there's no point in bothering to manage every single stroke in the state.

One common mistake I see a lot: pushing every single keystroke into a server state manager like TanStack Query. To me, form drafts are client state. Keep them there until the mutation actually succeeds. Once it's a success, then you sync with the server. Don't mix the two.

So, when do you actually need a library like React Hook Form?

I usually only reach for a library once the form starts getting complex. I'm talking about many fields with intricate validation rules, field arrays (like a shopping list where you can add rows), or needing to automatically map API errors back to specific fields. If it's just a simple three-field newsletter signup, pulling in a big library feels like overkill.

Not everything needs to be strictly controlled. Overusing controlled components without a good reason just makes an app feel heavy and rigid. On the flip side, relying too much on uncontrolled components can make you lose grip on the user experience if your validation isn't handled well.

The bottom line: if the UI needs to react while they type, go controlled. If you just need the data when they're done, go uncontrolled.