React FAQ: Top 75 Questions
8. What are controlled vs. uncontrolled components in React forms?
When dealing with forms in React, handling user input is crucial. React provides two main approaches for form components: **controlled components** and **uncontrolled components**, differing in how they manage form data.
Feature | Controlled Components | Uncontrolled Components |
---|---|---|
Data Handling | Form data is handled by the React component's state. | Form data is handled by the DOM itself. |
Value Source | The `value` of the form input is always driven by React state. | The `value` of the form input is managed by the browser. |
Updates | Every state update triggers a re-render, keeping the input value in sync with the state. | Updates happen directly in the DOM; React needs to be told to read the value (e.g., on form submission). |
Control | React component has full control over the input field (its value, validation, etc.). | Less control for React; relies on DOM behavior. |
Common Use Case | Most common approach for modern React forms. Ideal for real-time validation, dynamic inputs, manipulating input value. | Simpler for very basic forms where you only need the value once (e.g., on submit), or when integrating with non-React code. |
Mechanism | Input element's `value` prop is set by state, and `onChange` handler updates that state. | Input element's `defaultValue` (for initial value) and `ref` to read the current value. |
Complexity | Slightly more boilerplate (state, `onChange` handler) but offers more power. | Less boilerplate but less power and more direct interaction with DOM. |
import React, { useState, useRef } from 'react';
import ReactDOM from 'react-dom/client';
// Controlled Component Example
function ControlledForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = (event) => {
event.preventDefault(); // Prevent default form submission
alert(`Controlled Form Submitted:\nName: ${name}\nEmail: ${email}`);
// Clear form after submission
setName('');
setEmail('');
};
return (
<div>
<h3>Controlled Component</h3>
<form onSubmit={handleSubmit}>
<label>
Name:
<input
type="text"
value={name} {/* Value is controlled by React state */}
onChange={(e) => setName(e.target.value)} {/* Updates state on every change */}
/>
</label>
<br />
<label>
Email:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<br />
<button type="submit">Submit Controlled</button>
</form>
<p>Current Name (state): {name}</p>
</div>
);
}
// Uncontrolled Component Example
function UncontrolledForm() {
const nameRef = useRef(null); // Create a ref for the name input
const emailRef = useRef(null); // Create a ref for the email input
const handleSubmit = (event) => {
event.preventDefault();
// Access input values directly via refs
const name = nameRef.current.value;
const email = emailRef.current.value;
alert(`Uncontrolled Form Submitted:\nName: ${name}\nEmail: ${email}`);
// You'd typically not clear inputs this way for uncontrolled
// If you need to clear, you might reset the form or manipulate DOM directly
// nameRef.current.value = ''; // Not recommended practice
};
return (
<div>
<h3>Uncontrolled Component</h3>
<form onSubmit={handleSubmit}>
<label>
Name:
<input
type="text"
defaultValue="Default Name" {/* Initial value, not controlled by React state */}
ref={nameRef} {/* Attach ref to the input */}
/>
</label>
<br />
<label>
Email:
<input
type="email"
defaultValue="default@example.com"
ref={emailRef}
/>
</label>
<br />
<button type="submit">Submit Uncontrolled</button>
</form>
</div>
);
}
function App() {
return (
<div>
<ControlledForm />
<hr />
<UncontrolledForm />
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root-forms'));
root.render(<App />);
Explanation of the Code:
-
**`ControlledForm` (Controlled Component):**
- The `name` and `email` input values are tied to `useState` hooks.
- The `value` prop of the input is set to the state variable (`value={name}`).
- The `onChange` handler updates the state (`setName(e.target.value)`) whenever the input changes. This creates a "single source of truth" for the input's value: the React state.
- This allows for immediate access to the current input value, real-time validation, and easy manipulation of the input programmatically.
-
**`UncontrolledForm` (Uncontrolled Component):**
- The input fields use `defaultValue` for initial values (if any) and are *not* tied to React state with a `value` prop.
- `useRef` hooks are used to get a direct reference to the DOM input elements (`nameRef.current`, `emailRef.current`).
- When the form is submitted, the values are read directly from the DOM elements using `nameRef.current.value`.
- React has less control over the input's value in real-time, only interacting with it when explicitly told (e.g., on submit).
For most React applications, **controlled components are the preferred and recommended approach** because they offer more predictable behavior, easier debugging, and greater control over form data and user interactions.