Controlled components are the React-recommended way to handle form inputs. In this approach, form data is handled by React state, giving you more control, flexibility, and power when building interactive forms.
A controlled component is a form element whose value is controlled by React state. The component doesn't maintain its own internal state; instead, it relies on props passed from its parent component.
This pattern follows React's "single source of truth" principle, where the component state is the authoritative data source.
Here's a basic example of a controlled text input:
1import React, { useState } from "react"; 2 3function ControlledInput() { 4 const [value, setValue] = useState(""); 5 6 const handleChange = (event) => { 7 setValue(event.target.value); 8 }; 9 10 return ( 11 <div> 12 <input type="text" value={value} onChange={handleChange} /> 13 <p>Current value: {value}</p> 14 </div> 15 ); 16}
Notice these key characteristics:
value
attribute is set to a state variableonChange
handler updates the state when the user typesFor forms with multiple fields, you can use a single state object:
1import React, { useState } from "react"; 2 3function RegistrationForm() { 4 const [formData, setFormData] = useState({ 5 firstName: "", 6 lastName: "", 7 email: "", 8 password: "" 9 }); 10 11 const handleChange = (event) => { 12 const { name, value } = event.target; 13 setFormData({ 14 ...formData, // Preserve existing form data 15 [name]: value // Update only the changed field 16 }); 17 }; 18 19 const handleSubmit = (event) => { 20 event.preventDefault(); 21 console.log("Form submitted:", formData); 22 // Process the form data (e.g., API call) 23 }; 24 25 return ( 26 <form onSubmit={handleSubmit}> 27 <div> 28 <label htmlFor="firstName">First Name:</label> 29 <input 30 type="text" 31 id="firstName" 32 name="firstName" 33 value={formData.firstName} 34 onChange={handleChange} 35 /> 36 </div> 37 38 <div> 39 <label htmlFor="lastName">Last Name:</label> 40 <input 41 type="text" 42 id="lastName" 43 name="lastName" 44 value={formData.lastName} 45 onChange={handleChange} 46 /> 47 </div> 48 49 <div> 50 <label htmlFor="email">Email:</label> 51 <input 52 type="email" 53 id="email" 54 name="email" 55 value={formData.email} 56 onChange={handleChange} 57 /> 58 </div> 59 60 <div> 61 <label htmlFor="password">Password:</label> 62 <input 63 type="password" 64 id="password" 65 name="password" 66 value={formData.password} 67 onChange={handleChange} 68 /> 69 </div> 70 71 <button type="submit">Register</button> 72 </form> 73 ); 74}
Controlled components work with all HTML form elements. Here are some common examples:
1function ControlledCheckbox() { 2 const [isChecked, setIsChecked] = useState(false); 3 4 return ( 5 <label> 6 <input 7 type="checkbox" 8 checked={isChecked} 9 onChange={(e) => setIsChecked(e.target.checked)} 10 /> 11 I agree to the terms 12 </label> 13 ); 14}
1function ControlledRadioGroup() { 2 const [selectedOption, setSelectedOption] = useState("option1"); 3 4 const handleOptionChange = (e) => { 5 setSelectedOption(e.target.value); 6 }; 7 8 return ( 9 <div> 10 <label> 11 <input 12 type="radio" 13 value="option1" 14 checked={selectedOption === "option1"} 15 onChange={handleOptionChange} 16 /> 17 Option 1 18 </label> 19 20 <label> 21 <input 22 type="radio" 23 value="option2" 24 checked={selectedOption === "option2"} 25 onChange={handleOptionChange} 26 /> 27 Option 2 28 </label> 29 </div> 30 ); 31}
1function ControlledSelect() { 2 const [selectedValue, setSelectedValue] = useState("apple"); 3 4 return ( 5 <select 6 value={selectedValue} 7 onChange={(e) => setSelectedValue(e.target.value)} 8 > 9 <option value="apple">Apple</option> 10 <option value="banana">Banana</option> 11 <option value="orange">Orange</option> 12 </select> 13 ); 14}
A major advantage of controlled components is the ability to provide immediate feedback to users:
1function ValidatedInput() { 2 const [email, setEmail] = useState(""); 3 const [isValid, setIsValid] = useState(true); 4 5 const validateEmail = (email) => { 6 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 7 return emailRegex.test(email); 8 }; 9 10 const handleChange = (e) => { 11 const newEmail = e.target.value; 12 setEmail(newEmail); 13 setIsValid(validateEmail(newEmail) || newEmail === ""); 14 }; 15 16 return ( 17 <div> 18 <input 19 type="email" 20 value={email} 21 onChange={handleChange} 22 style={{ borderColor: isValid ? "initial" : "red" }} 23 /> 24 {!isValid && <p style={{ color: "red" }}>Please enter a valid email address</p>} 25 </div> 26 ); 27}
required
, min
, max
, etc.) for accessibilityControlled components give you precise control over form behavior in React applications. By connecting form elements to React state, you can:
While they require more initial code than uncontrolled components, the benefits of controlled components far outweigh the costs for most applications, especially those with complex forms or validation requirements.