What is Lifting State Up in React?

In React, managing state efficiently is crucial for building dynamic and bug-free user interfaces. This article explains the concept of “lifting state up”—a fundamental React pattern used to share data between components. You will learn what lifting state up means, why it is necessary for maintaining a single source of truth, and how to implement it using a practical code example.

The Core Concept

React relies on a unidirectional data flow, meaning data flows downward from parent to child components. However, sibling components often need to share and react to the same state. Because React does not allow direct sibling-to-sibling communication, you must “lift” the shared state up to their closest common ancestor.

The parent component holds the state and passes it down to the children as props. When a child component needs to update that state, the parent passes down a callback function that the child can trigger.

Why Lift State Up?

A Practical Example

Consider a scenario with two sibling components: an input field for a temperature and a display that shows whether the water would boil at that temperature.

Instead of both components keeping their own copy of the temperature, the state is lifted up to the parent component, Calculator.

import React, { useState } from 'react';

// Sibling A: The Input Component
function TemperatureInput({ temperature, onTemperatureChange }) {
  return (
    <fieldset>
      <legend>Enter temperature in Celsius:</legend>
      <input
        value={temperature}
        onChange={(e) => onTemperatureChange(e.target.value)}
      />
    </fieldset>
  );
}

// Sibling B: The Display Component
function BoilingVerdict({ celsius }) {
  if (celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

// Common Ancestor: Holds the Lifted State
export default function Calculator() {
  const [temperature, setTemperature] = useState('');

  const handleTemperatureChange = (newTemp) => {
    setTemperature(newTemp);
  };

  return (
    <div>
      <TemperatureInput 
        temperature={temperature} 
        onTemperatureChange={handleTemperatureChange} 
      />
      <BoilingVerdict 
        celsius={parseFloat(temperature)} 
      />
    </div>
  );
}

In this example, the Calculator component acts as the single source of truth. It passes the temperature state down to both child components. When the user types in the TemperatureInput, it triggers the onTemperatureChange callback, updating the state in the Calculator. Consequently, both sibling components receive the updated values instantly.