Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
ReactNext.jsJune 24, 20263 min read

React form handling: Controlled vs. Uncontrolled Components

React form handling doesn't have to be complex. Learn the trade-offs between controlled and uncontrolled components to decide when to sync your UI state.

ReactFrontendJavaScriptWeb DevelopmentPerformanceState ManagementNext.jsTutorial

During my first year as a frontend dev, I treated every <input> like it needed a useState hook. I thought if it wasn’t in state, it wasn’t "React-y" enough. I spent hours debugging performance issues in a simple search bar that re-rendered the entire page on every keystroke, only to realize I was fighting the DOM instead of working with it.

Understanding the React Form Handling Spectrum

The choice between controlled and uncontrolled components is a fundamental pillar of React state management. When you build forms, you aren't just capturing data; you're deciding who the "source of truth" is: the React component tree or the browser's internal DOM state.

The Controlled Approach

In a controlled component, the input's value is driven entirely by React state. Every onChange event triggers a state update, which forces a re-render, effectively pushing the new value back down into the input.

JAVASCRIPT
const [name, setName] = useState(CE9178">'');

<input 
  value={name} 
  onChange={(e) => setName(e.target.value)} 
/>

This is powerful because it gives you immediate access to the value. You can perform real-time validation, disable buttons based on input length, or format the text as the user types. However, as I learned the hard way, if you have a form with 50+ inputs, every single keystroke triggers a component lifecycle cycle. If your parent component isn't optimized, you'll see a noticeable lag—often around 100ms or more on lower-end devices.

The Uncontrolled Approach

Uncontrolled components let the DOM handle the state. You use a useRef hook to grab the value only when you actually need it, like when the user clicks "Submit."

JAVASCRIPT
const inputRef = useRef(null);

const handleSubmit = () => {
  console.log(inputRef.current.value);
};

<input ref={inputRef} />

This is significantly faster for large forms because you skip the overhead of constant re-renders. It’s the "old school" way, but it's incredibly efficient for simple data entry tasks where you don't need to manipulate the UI based on every character typed.

Choosing Your Strategy

I’ve found that juniors often over-engineer simple forms. Here is how I decide which path to take:

  1. Use Controlled Components when:

    • You need real-time validation (e.g., "Password too weak").
    • You need to conditionally disable the submit button.
    • You are building a complex UI that changes based on input (e.g., dynamic fields).
    • You need to support "instant search" filters.
  2. Use Uncontrolled Components when:

    • You have a large form with many inputs.
    • You only need the data upon submission.
    • You want to minimize re-renders to keep the app snappy.
    • You are migrating legacy forms into React.

Why the Mental Model Matters

When you understand React rendering: Why state updates re-run your components, you realize that every state update has a cost. If you're building a massive dashboard, keeping every field in global state might trigger unnecessary reconciliation cycles.

I once tried to move a massive multi-step form entirely into a global Redux store. It broke because the performance overhead of syncing the DOM with the store on every single character was too high. We eventually switched to uncontrolled inputs for the heavy lifting and only synced the final output to our state management layer. It was a massive win for performance, dropping our input latency by roughly 40ms.

A Few Caveats

Don't get caught in the trap of thinking one is "better." They are just tools. Uncontrolled components are harder to test with certain libraries like React Testing Library if you're trying to simulate specific user interactions, while controlled components require more boilerplate code.

Next time you start a form, ask yourself: "Does the UI need to change while the user types?" If the answer is no, stop reaching for useState. Use a ref, grab the value at the end, and save your app the extra work. I'm still refining my own approach—sometimes I'll start with uncontrolled to get a feature out the door, then refactor to controlled if the requirements shift toward complex validation. It's okay to change your mind as the feature evolves.

Back to Blog

Similar Posts

ReactNext.jsJune 24, 20264 min read

React reconciliation explained: How to optimize your DOM updates

React reconciliation is the engine behind your UI updates. Learn how DOM diffing works and how to minimize mutations to keep your React app fast.

Read more
ReactNext.js
June 24, 2026
4 min read

React performance: When to use useMemo and useCallback

React performance depends on knowing when to memoize. Learn the mental model for using useMemo and useCallback effectively without falling into optimization traps.

Read more
ReactNext.jsJune 24, 20264 min read

React State Management: Mastering Component Initialization and Syncing

React state management during component initialization often leads to bugs. Learn how to handle prop-to-state syncing correctly using standard React patterns.

Read more