Skip to content
Laviyx Learn

Try

React course menu

Lifting State Up in React: Sharing State Between Components

Share state between React components by lifting it up to their closest common parent: passing state down as props and setter functions as event props, a single source of truth, deciding where state lives, and when to reach for context.

Beginner8 min readUpdated
On this page

Each component's state is private. When two components need the same data (for example a search box and the list it filters), move that state to their closest common parent and pass it down. This is called lifting state up.

The problem#

Two accordion panels where only one should be open at a time. If each panel keeps its own isOpen state, they cannot know about each other:

JSX
import { useState } from "react";

function Panel({ title, children }) {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <section style={{ border: "1px solid #ddd", padding: 8, marginBottom: 6 }}>
      <strong>{title}</strong>{" "}
      <button onClick={() => setIsOpen(!isOpen)}>{isOpen ? "Hide" : "Show"}</button>
      {isOpen && <p>{children}</p>}
    </section>
  );
}

export default function App() {
  return (
    <div>
      <Panel title="Delivery">Orders arrive in 3–5 days.</Panel>
      <Panel title="Returns">Return within 10 days of delivery.</Panel>
    </div>
  );
}
Result

Both can be open at once.

The solution: lift the state#

  1. Remove the state from the children.
  2. Add it to the common parent, and pass it down as props.
  3. Pass down functions so children can ask the parent to change it.
JSX
import { useState } from "react";

function Panel({ title, isOpen, onShow, children }) {
  return (
    <section style={{ border: "1px solid #ddd", padding: 8, marginBottom: 6 }}>
      <strong>{title}</strong>{" "}
      {isOpen ? <p>{children}</p> : <button onClick={onShow}>Show</button>}
    </section>
  );
}

export default function App() {
  const [openIndex, setOpenIndex] = useState(0);
  return (
    <div>
      <Panel title="Delivery" isOpen={openIndex === 0} onShow={() => setOpenIndex(0)}>
        Orders arrive in 3–5 days.
      </Panel>
      <Panel title="Returns" isOpen={openIndex === 1} onShow={() => setOpenIndex(1)}>
        Return within 10 days of delivery.
      </Panel>
    </div>
  );
}
Result

Now the parent decides which panel is open, so opening one closes the other. The Panel components are controlled by their parent through props.

Search box and list#

The same pattern for a filter: the search text is needed by both the input and the results, so it lives in their parent.

JSX
import { useState } from "react";

const RECIPES = ["Paneer butter masala", "Masala dosa", "Veg biryani", "Dal makhani", "Paneer tikka"];

function SearchBar({ query, onQueryChange }) {
  return <input value={query} onChange={(e) => onQueryChange(e.target.value)} placeholder="Search recipes" />;
}

function RecipeList({ recipes }) {
  if (recipes.length === 0) return <p>No recipes found.</p>;
  return <ul>{recipes.map((r) => <li key={r}>{r}</li>)}</ul>;
}

export default function App() {
  const [query, setQuery] = useState("");
  const visible = RECIPES.filter((r) => r.toLowerCase().includes(query.toLowerCase()));

  return (
    <div>
      <SearchBar query={query} onQueryChange={setQuery} />
      <p>{visible.length} recipes</p>
      <RecipeList recipes={visible} />
    </div>
  );
}
Result

Data flows down (query, recipes) and events flow up (onQueryChange). This one-way data flow makes React apps predictable: to find out why something shows a value, follow the props up to the state.

A single source of truth#

Every piece of state should live in exactly one component, the one that "owns" it. Avoid copying props into state (useState(props.value)): the copy does not update when the prop changes, and then two components disagree.

Where should state live?#

For each piece of state:

  1. Find every component that renders something based on it.
  2. Find their closest common parent.
  3. Put the state there (or higher, if that makes more sense).

If you end up passing props through many layers that do not use them ("prop drilling"), consider context.

Common mistakes#

Practice#

Practice exercise

Build a temperature converter with two inputs, Celsius and Fahrenheit. Typing in either updates the other. Keep a single piece of state in the parent (the temperature in Celsius) and calculate Fahrenheit from it.

Show hint

Store celsius as a string in state. Fahrenheit is celsius * 9 / 5 + 32. When the Fahrenheit input changes, convert back with (f - 32) * 5 / 9. Round displayed values to one decimal.

Show solution
JSX
import { useState } from "react";

function TemperatureInput({ label, value, onChange }) {
  return (
    <label style={{ display: "block", marginBottom: 8 }}>
      {label}: <input type="number" value={value} onChange={(e) => onChange(e.target.value)} />
    </label>
  );
}

const round1 = (n) => String(Math.round(n * 10) / 10);

export default function App() {
  const [celsius, setCelsius] = useState("25");
  const fahrenheit = celsius === "" ? "" : round1((Number(celsius) * 9) / 5 + 32);

  return (
    <div>
      <TemperatureInput label="Celsius" value={celsius} onChange={setCelsius} />
      <TemperatureInput
        label="Fahrenheit"
        value={fahrenheit}
        onChange={(f) => setCelsius(f === "" ? "" : round1(((Number(f) - 32) * 5) / 9))}
      />
    </div>
  );
}
Result

Because Celsius is stored rounded to one decimal, some Fahrenheit values change slightly as you type them: 100 °F becomes 37.8 °C and shows 100 again, but 2 °F becomes −16.7 °C, which converts back to 1.9 °F. Storing which field was edited plus its exact text avoids this, a common refinement.

Key takeaway#

Next: Effects with useEffect.

Finished this lesson?