Lifting State Up

Sometimes two components that don't have a parent/child relationship to each other need to stay in sync — a filter input and a list it filters, or two counters that should share one total. The fix is always the same: move the state up to their closest common parent, and pass it back down as props.

The problem: sibling components can't share state directly

If TemperatureInput and TemperatureDisplay are both children of App, neither one can read the other's state directly — React data flows one direction, down through props. The fix is to have App own the shared state instead of either child owning its own copy.

JSX App.jsx
import { useState } from "react";

function TemperatureInput({ value, onChange }) {
  return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}

function TemperatureDisplay({ celsius }) {
  const fahrenheit = celsius === "" ? "" : (celsius * 9) / 5 + 32;
  return <p>{fahrenheit}&deg;F</p>;
}

function App() {
  const [celsius, setCelsius] = useState("");

  return (
    <>
      <TemperatureInput value={celsius} onChange={setCelsius} />
      <TemperatureDisplay celsius={celsius} />
    </>
  );
}
Rendered output
Typing "100" into the input immediately shows "212°F"
below it, updating on every keystroke.

App is the closest common parent of TemperatureInput and TemperatureDisplay, so celsius lives in App's state. TemperatureInput doesn't own the value at all — it receives the current value as a prop and calls onChange (which is really setCelsius, passed down) whenever it changes. Neither child has its own copy of the truth; App is the single source of it.

The general pattern

Whenever you find yourself wondering "how do I get this value from one component into a sibling," the answer is almost always: move that piece of state up to their shared parent, and pass it down — the current value as a prop, and a setter function as another prop for updating it.

Course complete: that covers the fundamentals of React — JSX and why it isn't HTML, components and props, giving a component its own state with useState, handling events, conditionally rendering UI, turning arrays into lists with .map() and keys, running side effects with useEffect, controlled form inputs, and lifting shared state up to a common parent. From here, the natural next steps are a routing library for multi-page apps and a broader look at state management once a single lifted useState stops being enough.