A Single Input Box Exposes React's State Snapshot and Lazy Init Traps
These two `useState` behaviors cause real bugs and real lag in production forms and dashboards. Knowing that state is a snapshot eliminates a whole category of stale-closure confusion, and lazy initialization is a one-character fix that prevents O(n) work from running on every keystroke.
A user filtering demo ties together two React behaviors that trip up newcomers. State is a per-render snapshot, so logging a variable immediately after `setState` still shows the old value; the update only takes effect in the next render. The fix for consecutive updates is a functional updater that reads the pending previous state.
The same demo surfaces a performance trap. Passing a function call like `useState(heavyCalculation())` executes the heavy work on every render even though React discards the result after mount. Wrapping it in an arrow function turns it into a lazy initializer that runs exactly once.
Derived data, controlled inputs, and stable list keys round out the mental model. Filtered results are computed during render rather than duplicated into state, the input's value is driven entirely by React state, and `user.id` keys let React correctly reconcile list changes.
The lazy-initializer pitfall is easy to miss because React silently discards the recomputed value after mount, so the app works correctly but pays an invisible tax on every render.
Derived data is a design rule that eliminates an entire class of synchronization bugs: when you stop duplicating state, you stop needing to keep two copies in agreement.
The snapshot model is simpler than it sounds once you stop thinking of state as a mutable variable and start treating each render as a frame with its own frozen values.