Controlled vs. Uncontrolled Components and When React.memo Actually Stops a Re-render
Misunderstanding the controlled/uncontrolled boundary leads to forms that either lose input or fight the DOM. Misunderstanding memo's shallow comparison leads developers to wrap components in memo, see no improvement, and conclude React optimization is broken when the real problem is unstable prop references.
A controlled input runs a tight data loop: a keystroke updates the DOM, fires onChange, writes to React state, schedules a re-render, and then React flushes the DOM again with its own state. The framework never trusts the browser; state is the sole authority and the DOM is just a projection. Uncontrolled components flip this by handing a ref to the real DOM node and reading values directly, which suits submit-time workflows where real-time validation isn't needed.
Multi-field forms expose the trade-off. A single useState object plus one dynamic handleChange can manage ten fields, whereas the uncontrolled approach demands a separate useRef for every field, scaling poorly. The LoginForm demo layers validation on top, deriving an isValid boolean from form and error state rather than storing it separately, which eliminates synchronization bugs.
React.memo wraps a component in a shallow comparison: if every prop passes a strict-equality check, the render is skipped. The catch is that objects, arrays, and functions created inline during a parent render get fresh references every time, so memo silently fails. That failure is the entire reason useCallback and useMemo exist.
The controlled pattern's 'React doesn't trust the DOM' framing explains why React feels heavy for simple forms: it reasserts control on every keystroke, even when the DOM already has the right value.
Choosing between controlled and uncontrolled is really a question of when you need the value. Real-time validation, formatting, or field linkage demands controlled; submit-and-forget forms work fine uncontrolled.
The isValid derivation pattern is under-taught. Many developers reach for useEffect to sync a separate isValid state, creating a second source of truth and the bugs that come with it.
React.memo's failure mode is silent and confusing. A component wrapped in memo that still re-renders on every parent render looks like a React bug, but it is almost always unstable prop references from inline functions or objects.