Optimizing React Forms - Beyond useState
Learn how to improve form performance in React by moving beyond useState
useState is usually the first hook anyone learns, and it’s easy to reach for it everywhere. That habit costs you re-renders. Here’s a login form as an example.
The Problem with useState
Every keystroke re-renders the whole component. The typical pattern:
const LoginForm = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e) => {
e.preventDefault();
// Handle login
console.log(email, password);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Login</button>
</form>
);
};
If you’re not validating on every keystroke, you don’t need to track the value on every keystroke. Read it on submit instead.
Solution 1: Using useRef
useRef holds a reference to the input without triggering re-renders:
const LoginForm = () => {
const emailRef = useRef();
const passwordRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
// Handle login
console.log(emailRef.current.value, passwordRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input type="email" ref={emailRef} />
<input type="password" ref={passwordRef} />
<button type="submit">Login</button>
</form>
);
};
Solution 2: Using FormData API
You often don’t need useRef either. If the inputs live inside a <form>, FormData covers it:
const LoginForm = () => {
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
// Handle login
console.log({
email: formData.get("email"),
password: formData.get("password"),
});
};
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" />
<input type="password" name="password" />
<button type="submit">Login</button>
</form>
);
};
When useState still makes sense
Reach for useState when you actually need the value during typing:
- Real-time validation
- Immediate feedback based on input
- The input value drives other UI
- Controlled components syncing with external state
Performance impact
Re-render counts for a form with 5 inputs:
| Approach | Re-renders per keystroke | Re-renders for 100 characters |
|---|---|---|
| useState | 5 | 500 |
| useRef | 0 | 0 |
| FormData | 0 | 0 |
Conclusion
useState isn’t wrong, it’s just overused for forms. If you don’t need the value mid-keystroke, useRef or FormData gives you the same result with zero re-renders and less code.
Before adding useState to a form input, check whether you actually need the value before submit. Usually you don’t.