Hey everyone, Prashuk here.
If you’ve been working with React for a while, you know that forms are usually the first thing that breaks when your app starts to grow.
I’ve spent a lot of time building dashboards and internal tools where a single page might have 50+ input fields, dynamic rows, and complex validation. To be honest, I’ve made every mistake possible—from making the UI feel like it's running on a 2005 Nokia to writing code that was a total nightmare to maintain.
Let's talk about how to actually handle large-scale forms without killing your performance or your sanity.
The "Controlled Component" trap
We’re all taught that "The React Way" is to use controlled components. You know the drill: value={state} and onChange={setState}.
In one of my projects—a heavy logistics management tool—I used this approach for a massive data-entry grid. Every single keystroke was triggering a re-render of the entire form. As the dataset grew to about 100 rows, the lag became unbearable. There was a noticeable 200ms delay between typing a letter and seeing it on the screen.
My takeaway: Controlled components are fine for a login page. But for large datasets? They are a performance killer because they force React to reconcile the entire component tree on every single character change.
When Uncontrolled actually wins
I used to think useRef was just for focusing inputs or messy DOM manipulations. But when I ran into that lag issue, I switched to an uncontrolled strategy.
By using useRef or just native FormData, you’re letting the DOM handle the state of the input. React doesn’t need to know about every "A, B, or C" you type. It only cares when you hit "Submit."
What worked for me was using uncontrolled components for the "bulk" of the data entry and only using state for the fields that actually needed immediate feedback (like showing/hiding other parts of the form). The difference in snappiness was night and day.
The "Sweet Spot": React Hook Form
If you’re building something serious, don't reinvent the wheel. After struggling with custom logic for months, I started using React Hook Form (RHF).
It’s basically the best of both worlds. It uses refs under the hood to prevent unnecessary re-renders, but it gives you a clean API to handle validation and submission.
In my experience, if you care about performance, RHF is the gold standard. It doesn't re-render the whole form just because one input changed.
A Practical Example (The optimized way)
Instead of putting everything in one giant state object, I started breaking forms down. Here’s a simple look at how I’d handle a high-performance input now:
import { useForm } from "react-hook-form";
const MassiveForm = () => {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => {
console.log("Real-world data:", data);
// Usually, I'd trigger a clean API call here
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
{/* Imagine 50 of these. RHF handles them like a champ. */}
<input
{...register("firstName")}
placeholder="First Name"
className="p-2 border rounded-md"
/>
<button type="submit" className="bg-blue-500 text-white p-2">
Save Changes
</button>
</form>
);
};
Don’t forget the UI/UX
Performance isn't just about execution time; it's about how the user feels.
I once had a form that was technically "fast," but because I didn't have loading states for the async validation, the user kept clicking the submit button multiple times.
- Debounce your API calls: If you’re checking if a username is taken, don't hit the server on every letter.
- Skeleton States: Use them while the form is initialising with large datasets.
- Error Focus: If a form has 50 fields and one is wrong, automatically scroll the user to that error. Small things like this make a huge difference in how "clean" your app feels.
My Best Practices for 2024
- Keep state local: Don't lift form state to Redux or a global Context unless you absolutely have to. It makes the whole app sluggish.
- Memoize wisely: If you have a list of 100 form rows, wrap the individual row components in
React.memo. - Validate on blur: Instead of validating as the user types (which is distracting), validate when they leave the field. It’s better for performance and less annoying for the user.
Final Thoughts
At the end of the day, your users don't care if your code follows "perfect" theory. They care if the input box lags when they type their name.
In my projects, moving away from "state-for-everything" was the biggest turning point. Use uncontrolled components where you can, leverage libraries like React Hook Form, and always, always test your forms with a throttled CPU in Chrome DevTools to see how they actually perform on slower devices.
What’s been your biggest headache with React forms? Let me know, I'd love to swap some horror stories.
— Prashuk