Suggest an editImprove this articleRefine the answer for “Lifting state up in React”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Lifting state up** moves shared state to the closest common parent of the components that need it. The parent owns the data, passes it down as props, and receives updates via callback props. Siblings stay in sync because they all render from the same source. ```tsx function Converter() { const [celsius, setCelsius] = useState(0); return ( <> <input value={celsius} onChange={e => setCelsius(+e.target.value)} /> <input value={celsius * 9/5 + 32} onChange={e => setCelsius((+e.target.value - 32) * 5/9)} /> </> ); } ``` **Key point:** children become controlled components. They read from props, not local state.Shown above the full answer for quick recall.Answer (EN)Image**Lifting state up** - a React pattern where shared state moves to the closest common parent of the components that need it, then flows back down as props. ## Theory ### TL;DR - Two siblings can't talk directly. The parent holds the data and passes it down. - Analogy: two kids, one thermostat. The parent holds the dial and adjusts it on request. - When parent state changes, all children re-render with the same fresh value. Local states drift apart. - Lift when 2+ siblings need the same data, or one input should drive another. ### Quick example ```tsx // ❌ Two inputs, two local states. They never sync. function CelsiusInput() { const [temp, setTemp] = useState(0); return <input value={temp} onChange={e => setTemp(+e.target.value)} />; } function FahrenheitInput() { const [temp, setTemp] = useState(32); // separate state, drifts apart return <input value={temp} onChange={e => setTemp(+e.target.value)} />; } // ✅ Parent owns state. Children are controlled. function TemperatureConverter() { const [celsius, setCelsius] = useState(0); return ( <> <input value={celsius} onChange={e => setCelsius(+e.target.value)} /> <input value={Math.round(celsius * 9 / 5 + 32)} onChange={e => setCelsius((+e.target.value - 32) * 5 / 9)} /> </> ); } // Change Celsius → Fahrenheit updates instantly. And vice versa. ``` ### Key difference After lifting, child components become **controlled**: they read from props and call parent handlers on change. Their local `useState` disappears. The parent re-renders and pushes fresh values to all children at the same time, so they stay in sync automatically. ### When to use - 2+ siblings need the same data → lift to their closest common parent - One input drives another (Celsius/Fahrenheit) → lift and compute derived values in parent - Form fields need to validate together → lift to the form container - A list and a filter read from the same array → lift the array Keep state local if only one component uses it. If you're passing props 3+ levels deep just to share state, that's prop drilling. At that point, Context or a state manager is the better call. ### How React handles it internally When `setState` fires in a parent, React schedules a re-render of that component and its subtree. Children receive fresh props in the render phase. If a prop value changed, React updates the DOM with `input.value = newValue`. All children update in the same pass, which is why they can't go out of sync. ### Common mistakes **Mixing local state with the incoming prop** ```tsx // ❌ Local state overrides the prop. Changes never reach the parent. function Child({ value, onChange }: { value: number; onChange: (v: number) => void }) { const [local, setLocal] = useState(0); // fights the prop! return <input value={local} onChange={e => setLocal(+e.target.value)} />; } // ✅ Controlled: use the prop directly function Child({ value, onChange }: { value: number; onChange: (v: number) => void }) { return <input value={value} onChange={e => onChange(+e.target.value)} />; } ``` In practice, this is the trickiest bug from this pattern. You pass a value as a prop, the child also has a local `useState`, and changes never reach the parent. React DevTools shows the prop updating while the input stays frozen. **Lifting too high** ```tsx // ❌ App re-renders on every keypress inside a login form <App formData={formData} setFormData={setFormData}> <LoginForm /> </App> ``` Lift only to the closest common parent. `LoginPage` is the right level, not `App`. Unrelated components shouldn't re-render because of a form field. **Mutating state instead of replacing it** ```tsx // ❌ Push mutates the array. React sees the same reference. No re-render. const addItem = () => { items.push('new'); setItems(items); }; // ✅ Create a new array const addItem = () => setItems([...items, 'new']); ``` ### Real-world usage - React docs temperature converter: the original lifting state up walkthrough uses Celsius/Fahrenheit exactly as above - TodoMVC: filter buttons and the todo list share the `todos` array lifted to `App` - Next.js search pages: query state lifted above both the search input and results list - Login forms: `showPassword` toggle and the `password` field share state in the form parent ### Follow-up questions **Q:** Why not use Redux or Context for everything? **A:** Redux adds boilerplate (actions, reducers, store setup) that isn't worth it for two siblings sharing data. Lift first. Reach for Context at 5+ consumers or 3+ nesting levels. Redux makes sense for app-wide state shared across many unrelated components. **Q:** How does a child send data up to the parent? **A:** Pass a callback prop: `onValueChange(data)`. The child calls it, the parent updates its state. That is the whole mechanism. **Q:** Does lifting state cause performance issues? **A:** Rarely. React batches state updates inside event handlers. If a specific subtree re-renders too often, wrap stable children in `React.memo`. Measure first with React DevTools Profiler before optimizing anything. **Q:** (Senior) When does lifting become prop drilling? Walk through the migration path. **A:** Around 10+ fields or 3+ nesting levels. Extract a `useForm` hook returning `{ values, updateField }`. Wrap the subtree in `<FormContext.Provider>` and consume with `useContext`. State still lives in one place, but components access it without long prop chains. ## Examples ### Basic: temperature converter ```tsx function TemperatureConverter() { const [celsius, setCelsius] = useState(0); const toFahrenheit = (c: number) => Math.round(c * 9 / 5 + 32); const toCelsius = (f: number) => (f - 32) * 5 / 9; return ( <div> <label> Celsius: <input type="number" value={celsius} onChange={e => setCelsius(+e.target.value)} /> </label> <label> Fahrenheit: <input type="number" value={toFahrenheit(celsius)} onChange={e => setCelsius(toCelsius(+e.target.value))} /> </label> </div> ); } // Type 100 in Celsius → Fahrenheit shows 212 immediately // Type 32 in Fahrenheit → Celsius shows 0 immediately ``` One component, one source of truth. Both inputs read from `celsius` and write back through converter functions. ### Intermediate: login form with shared state ```tsx function LoginForm() { const [formData, setFormData] = useState({ email: '', password: '', showPassword: false, }); const updateField = (field: keyof typeof formData) => (e: React.ChangeEvent<HTMLInputElement>) => setFormData({ ...formData, [field]: e.target.value }); return ( <form> <input value={formData.email} onChange={updateField('email')} placeholder="Email" /> <input type={formData.showPassword ? 'text' : 'password'} value={formData.password} onChange={updateField('password')} /> <label> <input type="checkbox" checked={formData.showPassword} onChange={e => setFormData({ ...formData, showPassword: e.target.checked })} /> Show password </label> </form> ); } // Toggle checkbox → password field switches between text and password type // All three fields share one state object at the form level ``` The checkbox and the password input are siblings. Without lifting, the checkbox couldn't control the input type. The form component owns everything, so the coordination is straightforward.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.