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.