LogoTRUONG PHAM
Home
Projects
Blogs
YouTube
Contact

Newsletter

Stay updated with technical artifacts and engineering insights.

LogoTRUONG PHAM

Building scalable software and sharing insights on technology & life.

Sitemap

  • Home
  • Projects
  • Blogs
  • YouTube
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Email
  • YouTube

© 2024 TRUONG PHAM. © All rights reserved.

Privacy PolicyTerms of Service
Back
Blog #29: When 'Controlled Component' turns into a performance burden
50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

Blog #29: When 'Controlled Component' turns into a performance burden

I used to believe every input in React must be a Controlled Component. Until I had to build a giant form with 100 fields.

TP
Truong PhamSoftware Engineer
PublishedJune 15, 2024

I admit I was once a "Control Freak" when writing React. I always believed the "standard React way" was for every input value to be stored in State and passed back to the value attribute of the Input.

I believed it because every tutorial on the internet taught it that way. "Controlled components give you absolute control over data, easy to validate, easy to format (like automatic capitalization)." It sounded perfect. But reality taught me a painful lesson when I participated in building an ERP system with "endless" Forms.

1. Context: When typing is an ordeal

The project had insurance registration forms with nearly 100 fields: Text, Number, Date, Select, Checkbox... Because I believed in the "standard," I wrapped the whole form in a large state object.

The result: Every time the User typed 1 character in the name input, the entire 100-field form re-rendered. On weak office computers, typing was delayed by 2-3 seconds. The User went crazy, and I was stuck.

2. Concept: Controlled vs Uncontrolled

The problem was I forced React to do something that the browser (plain HTML) does ten thousand times better: Managing input state.

// How I used to do it (Sounds "correct" but laggy)
const MyInput = () => {
  const [val, setVal] = useState("");
  return <input value={val} onChange={(e) => setVal(e.target.value)} />;
};

Why is it bad at large scale? Because every keystroke triggers a cycle: Keystroke -> State changes -> Re-render entire Component -> Update DOM. With 100 inputs reacting simultaneously, the browser's Main thread will be choked.

3. Practical Approach and Balance

A Junior will choose Controlled for everything because it's the first thing learned. They fear ref and consider Uncontrolled something "non-React."

Comparison of two ways:

  • The "Library Standard" way: Fully Controlled. Convenient for complex validation logic but a performance disaster for large forms. Clunky code due to too many onChange.
  • The Practical way (Uncontrolled / Hybrid):
    • Let the browser manage input values itself (using useRef or FormData).
    • Only use Controlled for fields that truly need an instant reaction (e.g., suggested search).
    • Use libraries like React Hook Form—where they use Uncontrolled to optimize re-renders but still provide an easy-to-use API.

Lesson Learned

I learned that: Loyalty to a theory is not as important as the user's actual experience.

  • When the old (Controlled) way is still correct: Small forms (< 10 fields), suggestive search boxes, inputs that need constant formatting (like money with commas).
  • When to avoid: Avoid using Controlled for large data forms, Dashboard pages with hundreds of input boxes.

Sometimes, the way to make React run fastest is... don't force it to manage what the browser already does too well.


Notes on the day I learned to let go of control to find smoothness.

Series • Part 29 of 50

50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES

NextBlog #30: A giant State Object doesn't make your code cleaner
Blog #28: The naivety of believing setState is an immediate assignment
24Blog #24: Failed Optimistic Update – When efforts to 'smooth' UI turn into UX disasters25Blog #25: Context API Perf Hit – When the 'standard React' solution betrays you26Blog #26: Don't let 'Single Source of Truth' become blind dogma27Blog #27: useEffect is not the place for doing all synchronizing logic28Blog #28: The naivety of believing setState is an immediate assignment29Blog #29: When 'Controlled Component' turns into a performance burdenReading30Blog #30: A giant State Object doesn't make your code cleaner31Blog #31: 1 AM and the Search for the 'Missing' File32Blog #32: Friday Afternoon, 800 Lines of Code, and a Miscalculation33Blog #33: 10 PM and the 'Infinite Loop' of Punishment34Blog #34: My Machine Runs Smooth, the User's Machine Smokes
TP

Written by Truong Pham

Software Engineer passionate about building high-performance systems and meaningful experiences.

Read more articles