Self-paced

Explore our extensive collection of courses designed to help you master various subjects and skills. Whether you're a beginner or an advanced learner, there's something here for everyone.

Bootcamp

Learn live

Join us for our free workshops, webinars, and other events to learn more about our programs and get started on your journey to becoming a developer.

Upcoming live events

Learning library

For all the self-taught geeks out there, here is our content library with most of the learning materials we have produced throughout the years.

It makes sense to start learning by reading and watching videos about fundamentals and how things work.

Search from all Lessons


← Back to Lessons

Uncontrolled Components in React Forms

What are Uncontrolled Components?
Implementing Uncontrolled Components
  • Basic Example

What are Uncontrolled Components?

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.

When to Use Uncontrolled Components

Uncontrolled components can be a good choice when:

  1. You have simple forms with few inputs
  2. You only need the form data upon submission
  3. You're integrating with non-React code or libraries
  4. You're working with file inputs
  5. You need to optimize for performance with many form fields

Implementing Uncontrolled Components

The primary tool for working with uncontrolled components is the React useRef hook (or createRef in class components).

Basic Example

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:

  • We use ref to create a reference to the DOM element
  • We use defaultValue instead of value for the initial value
  • Form data is accessed via ref.current.value when needed
  • No onChange handlers are required

Working with Different Form Elements

Text Inputs

1function UncontrolledTextInput() { 2 const inputRef = useRef(null); 3 4 return <input type="text" ref={inputRef} defaultValue="Default text" />; 5}

Checkboxes

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.

Select Dropdowns

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

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}

Default Values

There are two ways to specify the initial value of an uncontrolled component:

  1. Using the defaultValue attribute (or defaultChecked for checkboxes and radio buttons)
  2. Using the HTML attribute (though this is not the React way)
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} />

Complex Form Example

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.

Validation with Uncontrolled Components

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.

Controlled vs. Uncontrolled: Comparison

Here's a quick comparison to help you decide which approach to use:

FeatureControlledUncontrolled
Form data stored inReact stateDOM
Immediate access to input valueYesNo (requires ref access)
Real-time validationEasyMore complex
Code requiredMoreLess
Performance with many fieldsCan be slowerGenerally faster
Form resetEasy (reset state)Requires DOM manipulation
Integration with non-React codeMore difficultEasier

Best Practices for Uncontrolled Components

  1. Use uncontrolled components for simple forms with minimal interaction requirements
  2. Combine with HTML5 validation attributes (required, pattern, etc.)
  3. Use proper defaultValue/defaultChecked instead of the HTML value attribute
  4. Add key attributes to form elements if they need to be reset
  5. Implement form-level validation on submit rather than field-level validation
  6. Consider accessibility - error states are harder to communicate automatically

Conclusion

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.