React State Lifting, Explained with a Color Picker
State lifting eliminates an entire class of bugs where two components hold desynchronized copies of the same data. The pattern is a direct application of the single-source-of-truth principle, and getting the details right—immutable updates, string-to-number conversion, callback decoupling—prevents subtle runtime failures that TypeScript alone won't catch.
When a color picker is split into a display block and slider controls, the RGB state must live in their common parent. That parent becomes the single source of truth, passing the color down as read-only props and receiving updates through an `onColorUpdated` callback. The display component holds no state at all; the control component never mutates props directly but constructs a new object and hands it upward.
Three implementation details trip people up: `event.target.value` is a string, not a number, and the unary `+` operator is the cleanest fix. Spreading the existing color before overriding a single channel prevents data loss, because `setState` replaces the whole object rather than merging fields. Passing `setColor` directly as the callback keeps the control component decoupled from any specific parent, making it reusable and testable.
The checklist is short: if multiple components read the same state, lift it; if a child needs to change it, pass a callback; never let a child mutate state directly; and never store the same truth in two places.
The unary `+` operator for string-to-number conversion is concise but divisive: it's terse and idiomatic in some codebases, yet `Number(event.target.value)` is more explicit and less likely to be misread during code review.
The callback pattern's real payoff is testability—a component that receives `onColorUpdated` can be unit-tested with a mock without mounting its entire parent tree, which is harder when the child imports `setColor` directly.
The article's closing question about abstracting three sliders into a generic `ColorSlider` component hints at the next tension: DRY abstraction versus prop-drilling complexity. A generic slider would reduce duplication but introduce an indirection layer that may not be worth it for only three instances.