Suggest an editImprove this articleRefine the answer for “Controlled and uncontrolled components in React”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Controlled component** stores form value in React state and re-renders on every keystroke. Uncontrolled component stores the value in the DOM and you read it via ref only when needed. ```jsx // Controlled: value + onChange always paired <input value={email} onChange={(e) => setEmail(e.target.value)} /> // Uncontrolled: ref + defaultValue const ref = useRef(); <input ref={ref} defaultValue="" /> // ref.current.value on submit ``` **Key point:** use controlled for real-time validation; use uncontrolled for simple forms, file inputs, or third-party library integration.Shown above the full answer for quick recall.Answer (EN)Image**Controlled component** stores form value in React state and syncs it on every keystroke. **Uncontrolled component** lets the DOM hold the value and you read it via ref only when needed. ## Theory ### TL;DR - Controlled: React state is the source of truth, `onChange` fires on every keystroke and triggers a re-render - Uncontrolled: DOM holds the value, you read it via `ref` on demand - Analogy: controlled is a live chat that syncs every character to the server; uncontrolled is a paper form you fill out and hand in at the end - Use controlled for real-time validation or conditional rendering - Use uncontrolled for simple forms, file inputs, or third-party library integration ### Quick example ```jsx // CONTROLLED: React owns the value function Controlled() { const [name, setName] = useState(""); return ( <input value={name} onChange={(e) => setName(e.target.value)} /> // name is always in sync with what the user typed ); } // UNCONTROLLED: DOM owns the value function Uncontrolled() { const inputRef = useRef(); return ( <> <input ref={inputRef} /> <button onClick={() => console.log(inputRef.current.value)}> Submit </button> {/* value is read only on button click */} </> ); } ``` In controlled, `value` and `onChange` are always paired. In uncontrolled, the `ref` points directly to the DOM node. ### Key difference In a controlled component, React state is the single source of truth. Every keystroke fires `onChange`, updates state, and triggers a re-render with the new `value` prop. In an uncontrolled component, the browser updates the DOM directly without notifying React. When you read `inputRef.current.value`, you bypass React entirely and read from the DOM node. This means a controlled input is always in sync with React state, while an uncontrolled input can hold a value React knows nothing about. ### When to use **Controlled:** - Real-time validation - disable the submit button while an email field is invalid - Conditional rendering based on input - show autocomplete suggestions as the user types - Pre-filling fields with data from an API or props - Any situation where React needs to respond to every change **Uncontrolled:** - Simple forms where you only need the value on submit - File inputs (`<input type="file">`) - browsers block setting `value` programmatically for security reasons - Integrating third-party DOM libraries that manage their own state - Performance-sensitive forms with many fields where re-rendering on every keystroke causes visible lag ### Comparison table | Aspect | Controlled | Uncontrolled | |--------|-----------|-------------| | State location | React state | DOM element | | Sync timing | Every keystroke | On demand (via ref) | | Re-render | Yes, on every change | No, unless you trigger it manually | | Validation | Real-time possible | Only on submit | | Initial value prop | `value` | `defaultValue` | | When to use | Validation, conditional UI, pre-fill | Simple forms, file inputs, third-party libs | ### How it works internally When you type in a controlled input, the browser fires an `onChange` event. React's handler calls [`useState`](/questions/usestate) setter, schedules a re-render, diffs the virtual DOM, and patches the actual DOM with the new `value` prop. Everything happens in the same event cycle. For uncontrolled inputs, the browser updates the DOM directly. React's virtual DOM never sees the change. Reading [`inputRef.current.value`](/questions/useref) goes straight to the DOM node, skipping React completely. No re-render, no state update, no diffing. ### Common mistakes **1. Using `value` without `onChange`** ```jsx // WRONG: input becomes read-only <input value={name} /> // RIGHT: always pair them <input value={name} onChange={(e) => setName(e.target.value)} /> ``` React sets the value prop but has no way to update it. The user cannot type anything. React also logs a warning in the console about this exact issue. **2. Using `value` instead of `defaultValue` in uncontrolled components** ```jsx // WRONG: this creates a controlled input with no onChange handler <input ref={inputRef} value="initial" /> // RIGHT: defaultValue sets the initial value without taking control <input ref={inputRef} defaultValue="initial" /> ``` **3. Reading a ref before the component mounts** ```jsx // WRONG: inputRef.current is null during the first render function Form() { const inputRef = useRef(); const value = inputRef.current.value; // TypeError: null return <input ref={inputRef} />; } // RIGHT: read refs in event handlers or effects function Form() { const inputRef = useRef(); const handleClick = () => { console.log(inputRef.current.value); // safe here }; return <input ref={inputRef} />; } ``` **4. Initializing controlled state as `undefined`** ```jsx // WRONG: undefined means no value prop, so React treats input as uncontrolled const [name, setName] = useState(); <input value={name} onChange={(e) => setName(e.target.value)} /> // RIGHT: always initialize with an empty string const [name, setName] = useState(""); <input value={name} onChange={(e) => setName(e.target.value)} /> ``` React will warn: "A component is changing an uncontrolled input to be controlled." This happens because `undefined` equals no `value` prop on the first render. **5. Running expensive operations on every keystroke** ```jsx // SLOW: expensiveSearch fires on every character typed const handleChange = (e) => { setSearch(e.target.value); expensiveSearch(e.target.value); }; // BETTER: debounce the expensive part, update state immediately const handleSearch = useCallback( debounce((value) => expensiveSearch(value), 300), [] ); const handleChange = (e) => { setSearch(e.target.value); handleSearch(e.target.value); }; ``` ### Real-world usage - React Hook Form defaults to uncontrolled inputs for performance - it stores values in a ref, not state, which avoids re-rendering the form on every keystroke - Material-UI and Chakra UI pass `value` and `onChange` to all form components - controlled pattern by default - `<input type="file">` is always uncontrolled because browsers block programmatic `value` setting for security - Redux forms store field values in Redux state - the controlled pattern at a global level - Next.js Server Actions pair naturally with uncontrolled forms because you can pass a `FormData` object directly to the action without any state involved ### Follow-up questions **Q:** Why does React warn "You provided a `value` prop without an `onChange` handler"? **A:** Because the input becomes read-only. React sets the value but has no mechanism to update it, so users cannot type. React detects the mismatch early so you don't waste time debugging a frozen input field. **Q:** Can a component switch from uncontrolled to controlled during its lifetime? **A:** No. React throws an error if you change a component from uncontrolled to controlled or vice versa. Pick one pattern and keep it for the component's entire lifetime. **Q:** What is the actual performance difference? **A:** Controlled components re-render on every keystroke. With large forms or expensive render logic this causes visible lag. Uncontrolled components skip React's render cycle entirely, but you lose real-time validation. React Hook Form solves this by using uncontrolled inputs internally while exposing a controlled-like API. **Q:** A form with 50 fields - controlled or uncontrolled? **A:** Controlled with optimization. Split the form into smaller sub-components, memoize handlers with `useCallback`, and debounce any expensive operations. Or use React Hook Form, which handles this internally and gives you both performance and real-time validation. **Q:** (Senior) Why does React Hook Form default to uncontrolled components, and when would you override that? **A:** React Hook Form stores values in a ref instead of state to avoid re-rendering the entire form on every keystroke. You switch to controlled mode (via the `Controller` component or `useController` hook) when integrating with controlled UI libraries like Material-UI, or when you need conditional rendering that reacts to live input values. ## Examples ### Controlled form with live email validation ```jsx function SignupForm() { const [email, setEmail] = useState(""); const [error, setError] = useState(""); const handleChange = (e) => { const value = e.target.value; setEmail(value); if (value && !value.includes("@")) { setError("Invalid email"); } else { setError(""); } }; return ( <div> <input value={email} onChange={handleChange} placeholder="Email" /> {error && <span style={{ color: "red" }}>{error}</span>} {/* error appears and disappears as the user types */} </div> ); } ``` The error message reacts in real time because controlled inputs re-render on every keystroke. With an uncontrolled input you'd only catch the invalid email after the user hits submit. ### Uncontrolled form for simple submission ```jsx function ContactForm() { const nameRef = useRef(); const emailRef = useRef(); const handleSubmit = (e) => { e.preventDefault(); const data = { name: nameRef.current.value, email: emailRef.current.value, }; console.log(data); // both values read at once on submit }; return ( <form onSubmit={handleSubmit}> <input ref={nameRef} defaultValue="" placeholder="Name" /> <input ref={emailRef} defaultValue="" placeholder="Email" /> <button type="submit">Send</button> </form> ); } ``` No re-renders happen as the user types. React gets involved only at submit. This works well for simple contact forms where you don't need any live feedback. ### Why mixing both patterns causes confusion ```jsx // DON'T DO THIS function BadMix() { const [value, setValue] = useState(""); const inputRef = useRef(); return ( <> <input value={value} onChange={(e) => setValue(e.target.value)} ref={inputRef} /> <button onClick={() => console.log(inputRef.current.value)}> Log value </button> {/* ref reads the DOM node correctly here, but so does value state */} {/* two ways to get the same data - pick one */} </> ); } ``` The `ref` reads the DOM node correctly here. But the state already holds the same value. Using both creates unnecessary confusion about which one to trust. I have seen this pattern in code reviews often enough - it usually comes from developers who learned jQuery first and try to keep a foot in both worlds. Pick one approach and stay consistent.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.