Lifting State Up

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 2 min

Lifting State Up

“Lifting State Up” is a fundamental pattern in React. It is the process of moving state from a child component to a common parent component so that multiple children can share and synchronize that state.

React data flow is strictly one-way (top-down). Siblings cannot pass data directly to each other. If Sibling A and Sibling B both need access to the same piece of changing data, that data must live in their Parent.

The Problem

Imagine an application where you have two separate components: one that calculates temperature in Celsius, and one in Fahrenheit.

If they manage their own local state, they fall out of sync immediately.

// ❌ Out of sync
function CelsiusInput() {
  const [temp, setTemp] = useState('');
  return <input value={temp} onChange={e => setTemp(e.target.value)} />
}

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

function Calculator() {
  return (
    <div>
      <CelsiusInput />
      <FahrenheitInput />
    </div>
  )
}

If you type “100” into the Celsius input, the Fahrenheit input knows nothing about it.

The Solution: Lifting State Up

To fix this, we remove the useState from both child components. We “lift” that state up into the Calculator component.

The Calculator will hold the “single source of truth.” It will pass the current temperature down to the inputs as a prop, and it will pass a function down so the inputs can update the parent’s state.

// ✅ Synchronized
function TemperatureInput({ scale, temperature, onTemperatureChange }) {
  // This component has NO state. It is fully controlled by its parent.
  return (
    <label>
      Enter temperature in {scale}:
      <input 
        value={temperature} 
        onChange={e => onTemperatureChange(e.target.value)} 
      />
    </label>
  );
}

function Calculator() {
  // The state lives here now!
  const [temperature, setTemperature] = useState('');
  const [scale, setScale] = useState('c');

  const handleCelsiusChange = (temp) => {
    setScale('c');
    setTemperature(temp);
  };

  const handleFahrenheitChange = (temp) => {
    setScale('f');
    setTemperature(temp);
  };

  const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
  const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

  return (
    <div>
      <TemperatureInput 
        scale="Celsius" 
        temperature={celsius} 
        onTemperatureChange={handleCelsiusChange} 
      />
      <TemperatureInput 
        scale="Fahrenheit" 
        temperature={fahrenheit} 
        onTemperatureChange={handleFahrenheitChange} 
      />
    </div>
  );
}

When to Lift State Up

You should lift state up whenever you notice that two components need to reflect the same changing data.

Do not lift state up unnecessarily. If you lift state too high up the component tree (e.g., all the way to App.js just to pass it down 5 levels), you create “Prop Drilling.” If state needs to be shared across many deeply nested components, you should consider using the Context API or a global state manager instead.

Interview Questions

Q: When should you “lift state up”?
A: You should lift state up when two or more sibling components need to access, share, or synchronize the exact same piece of data. You move the state to their closest common ancestor, and pass the state (and the setter function) down to the siblings via props.