React forms can get complex quickly, especially as they grow in fields and complexity. Knowing when to use controlled and uncontrolled components effectively can simplify form management.
Trick: Combining Controlled and Uncontrolled Components for Form Optimization
Controlled Components in React are inputs that are fully controlled by React state (via useState), meaning every keystroke triggers a re-render. This is great for real-time validation but can become inefficient with large forms.
Uncontrolled Components use refs to access DOM values directly without requiring state, making them faster and ideal for inputs you don’t need to validate live.
Here’s a mix of both approaches to make an optimized form that’s still easy to manage.
Example: A Mixed-Controlled Form
Let’s create a form where some fields use controlled components and others use uncontrolled components.
import React, { useState, useRef } from "react";
const OptimizedForm = () => {
// Controlled input for real-time validation or tracking
const [username, setUsername] = useState("");
const [error, setError] = useState("");
// Uncontrolled inputs for fields that don't need live validation
const emailRef = useRef();
const passwordRef = useRef();
const handleUsernameChange = (e) => {
const value = e.target.value;
setUsername(value);
// Example validation
if (value.length < 3) {
setError("Username must be at least 3 characters.");
} else {
setError("");
}
};
const handleSubmit = (e) => {
e.preventDefault();
// Accessing values of uncontrolled inputs
const email = emailRef.current.value;
const password = passwordRef.current.value;
// Example form submission or validation logic
console.log("Username:", username);
console.log("Email:", email);
console.log("Password:", password);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username (Controlled):</label>
<input
type="text"
value={username}
onChange={handleUsernameChange}
/>
{error && <p style={{ color: "red" }}>{error}</p>}
</div>
<div>
<label>Email (Uncontrolled):</label>
<input type="email" ref={emailRef} />
</div>
<div>
<label>Password (Uncontrolled):</label>
<input type="password" ref={passwordRef} />
</div>
<button type="submit">Submit</button>
</form>
);
};
export default OptimizedForm;
Explanation:
- Controlled Component (Username):
We use useState to manage the username, enabling real-time validation (checking the character count).
This is ideal for fields that require live updates or validation.
- Uncontrolled Components (Email and Password):
We use useRef to get values directly from the DOM without managing them in state.
This is useful for fields like email or password where live validation might not be necessary.
- Form Submission:
- On form submit, we access the values of both controlled and uncontrolled inputs seamlessly.
Why This Trick is Useful
Performance: Reduces re-renders by avoiding state updates for every keystroke in large forms.
Simplicity: Keeps form logic cleaner and more maintainable by separating fields that need validation from those that don’t.
Mastering this technique will help you balance performance and responsiveness in React forms, especially in apps with complex or large forms!
You don't need refs either, you can access the form data directly from the forms onSubmit or onChange event.
Possibly related blog post.