Mastering Component State
Most interface bugs are state bugs: something remembered a value it should have derived, or two places disagreed about who owns the truth. Managing component state well is less about tools and more about a few habits applied consistently.
Derive before you store
The first question for any piece of state is whether it needs to exist. A filtered list, a validity flag, a formatted label — these are derivations of other state, and storing them means keeping two values in sync forever. If it can be computed during render, compute it during render.
Keep state as close as possible…
State that only one component reads should live in that component. Hoisting everything to a global store trades local reasoning for a big shared object that every feature can mutate. Local by default, lifted only when a real second consumer appears.
…but lift it the moment two owners appear
The classic smell is synchronization code: an effect that copies a value from one component into another. The moment two components need the same truth, move that truth to their closest common ancestor and pass it down. One owner, many readers.
Make impossible states impossible
A loading flag, an error flag and a data field can combine into states that should never happen — loaded and erroring at once, for example. Modeling the same information as a single status value with a payload removes the invalid combinations at the type level, and the rendering logic collapses into a simple switch.
Effects are for the outside world
Timers, subscriptions, network calls and manual DOM work belong in effects; everything else usually does not. If an effect only computes a value from props and stores it, that is a derivation wearing a disguise — delete it and compute inline.
State discipline compounds. Each derived value you refuse to store, each piece of state you keep local, each impossible state you rule out makes the next feature simpler to add.