How to Lift State Up in React

Lifting state up is a fundamental React pattern used to share state between multiple components that need to reflect the same changing data. This article provides a clear, step-by-step guide on how to implement this pattern by moving local state to the closest common ancestor component and passing it down to child components via props.

In React, data flows downward (one-way data binding). When two or more sibling components need access to the same state, you cannot pass data directly between them. Instead, you must “lift” that state up to their nearest common parent component. The parent then maintains the state and distributes it, along with functions to update it, down to the children.

Step 1: Identify the Common Ancestor

Look at the components that need to share data and locate their closest shared parent component in the component tree. This parent component will become the “source of truth” for the shared state.

Step 2: Define State in the Parent Component

Remove the local state (useState) from the child components and define it in the parent component instead.

import React, { useState } from 'react';

function ParentContainer() {
  const [sharedText, setSharedText] = useState('');

  return (
    <div>
      {/* Children will go here */}
    </div>
  );
}

Step 3: Pass State Down via Props

Pass the state value from the parent component down to the child components that need to read or display the data.

function TextDisplay({ text }) {
  return <p>Shared Value: {text}</p>;
}

Step 4: Pass the State Updater Function Down

To allow child components to change the state, pass a callback function (or the state setter itself) from the parent down as a prop.

function TextInput({ text, onTextChange }) {
  return (
    <input 
      type="text" 
      value={text} 
      onChange={(e) => onTextChange(e.target.value)} 
    />
  );
}

Complete Implementation Example

Here is how the complete integration looks when the state is lifted to the ParentContainer component:

import React, { useState } from 'react';

// Parent Component (Source of Truth)
function ParentContainer() {
  const [sharedText, setSharedText] = useState('');

  return (
    <div style={{ padding: '20px', border: '1px solid #ccc' }}>
      <h2>Parent Component</h2>
      <TextInput text={sharedText} onTextChange={setSharedText} />
      <TextDisplay text={sharedText} />
    </div>
  );
}

// Child Component 1 (Updates the state)
function TextInput({ text, onTextChange }) {
  return (
    <div>
      <label>Type here: </label>
      <input 
        type="text" 
        value={text} 
        onChange={(e) => onTextChange(e.target.value)} 
      />
    </div>
  );
}

// Child Component 2 (Reads the state)
function TextDisplay({ text }) {
  return (
    <div>
      <h3>Display Component</h3>
      <p>Current input value: {text || "(empty)"}</p>
    </div>
  );
}

export default ParentContainer;

By lifting the state up to ParentContainer, both TextInput and TextDisplay stay synchronized. Whenever the user types in the input field, the change handler triggers the state update in the parent, which triggers a re-render of both children with the updated values.