← Back to Chapters

Lifting State Up in React

⚛️ Lifting State Up in React

? Quick Overview

Sometimes, two or more components need to share or synchronize data. Instead of duplicating state in each component, React encourages a technique called “Lifting State Up.”

It means moving shared state to the closest common ancestor component, so all child components can use the same data via props. This keeps your UI consistent and easier to reason about.

? Goal: Single source of truth in a parent component

? Key Concepts

  • Shared State: Data used by multiple components.
  • Common Ancestor: The nearest parent that can own that state.
  • Top–Down Data Flow: Parent holds state, children receive it as props.
  • Setter Props: Parent passes callback functions so children can update parent state.

? Example: Without Lifting State

Here, each input component manages its own temperature. They are related, but their states are completely independent, so typing in one box does not affect the other.

? View Code Example
// Each input manages its own local temperature state
function CelsiusInput() {
const [temp, setTemp] = React.useState("");
return (
<input
value={temp}
onChange={(e) => setTemp(e.target.value)}
placeholder="Celsius"
/>
);
}

function FahrenheitInput() {
const [temp, setTemp] = React.useState("");
return (
<input
value={temp}
onChange={(e) => setTemp(e.target.value)}
placeholder="Fahrenheit"
/>
);
}

? Explanation

CelsiusInput and FahrenheitInput both store their own temp value. There is no connection between them, so updating one component does nothing to the other. This can cause inconsistent data when the values should actually be synchronized.

?️ Example: Lifting State to the Parent

Now we move the temperature state into a parent component. The parent becomes the single source of truth, and children receive values and setter callbacks via props.

? View Code Example
// Parent owns the temperature; children just display and update it
function TemperatureCalculator() {
const [celsius, setCelsius] = React.useState("");

const toFahrenheit = (c) => (c * 9) / 5 + 32;

return (
<div>
<CelsiusInput value={celsius} onChange={setCelsius} />
<FahrenheitDisplay fahrenheit={toFahrenheit(celsius)} />
</div>
);
}

function CelsiusInput({ value, onChange }) {
return (
<input
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="Celsius"
/>
);
}

function FahrenheitDisplay({ fahrenheit }) {
return <p>Fahrenheit: {fahrenheit}</p>;
}

? What’s Happening?

  • TemperatureCalculator holds the celsius state.
  • CelsiusInput receives the value and the setCelsius updater via props.
  • Whenever the user types in the input, the parent state updates.
  • FahrenheitDisplay always shows the latest converted value from that parent state.

Because both children depend on the same parent state, the UI stays in sync and easier to maintain.

? Why Lift State?

  • ✅ Keeps related components synchronized.
  • ✅ Reduces duplicate state and inconsistencies.
  • ✅ Makes debugging and data flow easier.
  • ✅ Follows React’s unidirectional (top-down) data flow.

⚙️ Identifying When to Lift State

  • When multiple components depend on the same data.
  • When an action in one component should affect another.
  • When sibling components need to communicate indirectly.
  • When you see the same state duplicated in multiple places.

? Example: Sibling Communication via Parent

Two sibling components (Sender and Receiver) share data by lifting state into their common parent. They never talk directly to each other — only through the parent.

? View Code Example
// Parent holds the shared message, siblings read/write through props
function Parent() {
const [message, setMessage] = React.useState("");

return (
<div>
<Sender onSend={setMessage} />
<Receiver message={message} />
</div>
);
}

function Sender({ onSend }) {
return (
<input
placeholder="Type message"
onChange={(e) => onSend(e.target.value)}
/>
);
}

function Receiver({ message }) {
return <p>Received: {message}</p>;
}

? How It Works

Sender updates the parent’s message state using onSend. Receiver reads that same state via the message prop. Any change in Sender instantly appears in Receiver.

? Benefits of Lifting State

  • Improved data consistency between components.
  • A clear, single source of truth — easier to manage and debug.
  • More predictable re-renders and performance tuning.
  • Cleaner mental model for how data flows through your app.

? Tips & Best Practices

  • Lift state only when needed — too much can make the parent too complex.
  • Use props to pass both data and setter functions to children.
  • Combine useEffect and callbacks when you need side effects tied to shared state.
  • For truly global state across many components, consider using the React Context API or a state library.

? Try It Yourself

  1. Create two input components: one for Celsius and one for Fahrenheit.
  2. Store the temperature in the parent component’s state.
  3. Convert automatically when either value changes (bi-directional conversion logic).
  4. Add a third component that displays a message like “Boiling Point Reached!” when above 100°C.

Goal: Practice lifting shared state to a parent component so that related or sibling components always stay in sync and follow React’s one-way data flow.