While controlled components are the recommended approach for most React form scenarios, uncontrolled components offer an alternative that can be simpler and more efficient in certain situations. This lesson explores when and how to use uncontrolled components effectively.
Uncontrolled components are form elements that maintain their own internal state. Instead of being driven by React state, they rely on the DOM to handle form data. Values are retrieved directly from the DOM when needed, typically during form submission.
The main characteristic of uncontrolled components is that React doesn't "control" the form elements' values during the component's lifecycle.
Uncontrolled components can be a good choice when:
The primary tool for working with uncontrolled components is the React useRef
hook (or createRef
in class components).
1import React, { useRef } from "react"; 2 3function SimpleUncontrolledForm() { 4 const nameInputRef = useRef(null); 5 const emailInputRef = useRef(null); 6 7 const handleSubmit = (e) => { 8 e.preventDefault(); 9 const formData = { 10 name: nameInputRef.current.value, 11 email: emailInputRef.current.value 12 }; 13 console.log("Form submitted with data:", formData); 14 // Process the form data 15 }; 16 17 return ( 18 <form onSubmit={handleSubmit}> 19 <div> 20 <label htmlFor="name">Name:</label> 21 <input 22 type="text" 23 id="name" 24 ref={nameInputRef} 25 defaultValue="" // Optional initial value 26 /> 27 </div> 28 29 <div> 30 <label htmlFor="email">Email:</label> 31 <input 32 type="email" 33 id="email" 34 ref={emailInputRef} 35 defaultValue="" 36 /> 37 </div> 38 39 <button type="submit">Submit</button> 40 </form> 41 ); 42}
Important points to note:
ref
to create a reference to the DOM elementdefaultValue
instead of value
for the initial valueref.current.value
when neededonChange
handlers are required1function UncontrolledTextInput() { 2 const inputRef = useRef(null); 3 4 return <input type="text" ref={inputRef} defaultValue="Default text" />; 5}
1function UncontrolledCheckbox() { 2 const checkboxRef = useRef(null); 3 4 const isChecked = () => { 5 return checkboxRef.current.checked; 6 }; 7 8 return ( 9 <> 10 <label> 11 <input 12 type="checkbox" 13 ref={checkboxRef} 14 defaultChecked={false} 15 /> 16 I agree to terms 17 </label> 18 <button onClick={() => alert(isChecked())}> 19 Check status 20 </button> 21 </> 22 ); 23}
Note: For checkboxes, use defaultChecked
instead of defaultValue
.
1function UncontrolledSelect() { 2 const selectRef = useRef(null); 3 4 const getCurrentValue = () => { 5 return selectRef.current.value; 6 }; 7 8 return ( 9 <> 10 <select ref={selectRef} defaultValue="orange"> 11 <option value="apple">Apple</option> 12 <option value="orange">Orange</option> 13 <option value="banana">Banana</option> 14 </select> 15 <button onClick={() => alert(getCurrentValue())}> 16 Get selected fruit 17 </button> 18 </> 19 ); 20}
File inputs are inherently uncontrolled in React, as their value is read-only:
1function FileInput() { 2 const fileInputRef = useRef(null); 3 4 const handleSubmit = (e) => { 5 e.preventDefault(); 6 const files = fileInputRef.current.files; 7 // Process files 8 console.log(`Selected ${files.length} files`); 9 }; 10 11 return ( 12 <form onSubmit={handleSubmit}> 13 <input type="file" ref={fileInputRef} multiple /> 14 <button type="submit">Upload</button> 15 </form> 16 ); 17}
There are two ways to specify the initial value of an uncontrolled component:
defaultValue
attribute (or defaultChecked
for checkboxes and radio buttons)1// Recommended React approach 2<input type="text" defaultValue="Initial value" ref={inputRef} /> 3 4// HTML approach (not recommended in React) 5<input type="text" value="Initial value" ref={inputRef} />
Here's a more complete example demonstrating an uncontrolled form with various input types:
1import React, { useRef } from "react"; 2 3function RegistrationForm() { 4 const formRef = useRef(null); 5 6 const handleSubmit = (e) => { 7 e.preventDefault(); 8 const form = formRef.current; 9 10 const formData = { 11 firstName: form.firstName.value, 12 lastName: form.lastName.value, 13 email: form.email.value, 14 gender: form.gender.value, 15 interests: Array.from(form.interests) 16 .filter(checkbox => checkbox.checked) 17 .map(checkbox => checkbox.value), 18 country: form.country.value, 19 comments: form.comments.value 20 }; 21 22 console.log("Form submitted with:", formData); 23 // Process the data 24 }; 25 26 return ( 27 <form ref={formRef} onSubmit={handleSubmit}> 28 <div> 29 <label htmlFor="firstName">First Name:</label> 30 <input type="text" id="firstName" name="firstName" defaultValue="" /> 31 </div> 32 33 <div> 34 <label htmlFor="lastName">Last Name:</label> 35 <input type="text" id="lastName" name="lastName" defaultValue="" /> 36 </div> 37 38 <div> 39 <label htmlFor="email">Email:</label> 40 <input type="email" id="email" name="email" defaultValue="" /> 41 </div> 42 43 <div> 44 <p>Gender:</p> 45 <label> 46 <input type="radio" name="gender" value="male" defaultChecked /> 47 Male 48 </label> 49 <label> 50 <input type="radio" name="gender" value="female" /> 51 Female 52 </label> 53 <label> 54 <input type="radio" name="gender" value="other" /> 55 Other 56 </label> 57 </div> 58 59 <div> 60 <p>Interests:</p> 61 <label> 62 <input type="checkbox" name="interests" value="sports" /> 63 Sports 64 </label> 65 <label> 66 <input type="checkbox" name="interests" value="music" /> 67 Music 68 </label> 69 <label> 70 <input type="checkbox" name="interests" value="reading" /> 71 Reading 72 </label> 73 </div> 74 75 <div> 76 <label htmlFor="country">Country:</label> 77 <select id="country" name="country" defaultValue=""> 78 <option value="" disabled>Select a country</option> 79 <option value="usa">United States</option> 80 <option value="canada">Canada</option> 81 <option value="mexico">Mexico</option> 82 <option value="other">Other</option> 83 </select> 84 </div> 85 86 <div> 87 <label htmlFor="comments">Comments:</label> 88 <textarea 89 id="comments" 90 name="comments" 91 defaultValue="" 92 rows="4" 93 /> 94 </div> 95 96 <button type="submit">Register</button> 97 </form> 98 ); 99}
In this example, we use a single ref for the entire form and access individual fields by their name attributes.
While uncontrolled components don't offer real-time validation as easily as controlled components, you can still implement validation:
1function ValidatedUncontrolledForm() { 2 const emailRef = useRef(null); 3 const [emailError, setEmailError] = useState(""); 4 5 const validateEmail = () => { 6 const email = emailRef.current.value; 7 const isValid = /\S+@\S+\.\S+/.test(email); 8 9 if (!isValid) { 10 setEmailError("Please enter a valid email address"); 11 return false; 12 } else { 13 setEmailError(""); 14 return true; 15 } 16 }; 17 18 const handleSubmit = (e) => { 19 e.preventDefault(); 20 if (validateEmail()) { 21 // Form submission logic 22 console.log("Form submitted with:", emailRef.current.value); 23 } 24 }; 25 26 return ( 27 <form onSubmit={handleSubmit}> 28 <div> 29 <label htmlFor="email">Email:</label> 30 <input 31 type="email" 32 id="email" 33 ref={emailRef} 34 onBlur={validateEmail} 35 /> 36 {emailError && <p className="error">{emailError}</p>} 37 </div> 38 39 <button type="submit">Submit</button> 40 </form> 41 ); 42}
In this pattern, we validate the input when it loses focus (onBlur
) or during form submission.
Here's a quick comparison to help you decide which approach to use:
Feature | Controlled | Uncontrolled |
---|---|---|
Form data stored in | React state | DOM |
Immediate access to input value | Yes | No (requires ref access) |
Real-time validation | Easy | More complex |
Code required | More | Less |
Performance with many fields | Can be slower | Generally faster |
Form reset | Easy (reset state) | Requires DOM manipulation |
Integration with non-React code | More difficult | Easier |
required
, pattern
, etc.)Uncontrolled components provide a simpler, sometimes more performant alternative to controlled forms in React. While they offer less control over the form data during the component lifecycle, they require less code and can be easier to implement for simple forms.
For most React applications, controlled components remain the recommended approach due to their predictability and integration with React's state model. However, understanding uncontrolled components gives you an additional tool in your React toolkit, especially useful for simple forms, file inputs, or when integrating with existing code.