Skip to solution
easyFrontend

Explain the concept of 'lifting state up' in React.

813 views
01

Understand the problem

Question presented to candidate: "Two sibling components need to stay in sync. Walk me through how you would handle that."

What a strong answer should cover:

  • Siblings cannot talk to each other — that would be a sideways flow React does not have. You move the shared state up to their nearest common ancestor.
  • The ancestor becomes the single source of truth and passes the value down to one child and a setter callback to the other.
  • It is the direct consequence of unidirectional data flow, not a separate technique.
  • Lift to the nearest common ancestor. Hoisting to the root re-renders the whole tree for a change two components care about.
  • The trade: more prop passing, and every intermediate component re-renders.
  • When lifting starts to hurt — deep trees, many consumers — the next steps are composition, Context, or a store. Reaching for Context immediately is the common overcorrection.
  • The mirror technique, moving state down, matters just as much: state that only one subtree needs should live there, not above it.
  • Controlled components are lifting state up applied to a form input: the value lives in the parent, not the DOM.

Clarifying questions expected:

  • "How far apart are these components, and does anything between them need the value?"
  • "Is this genuinely shared, or does each component need its own copy?"

Code / implementation expected: Yes — two inputs kept in sync through a shared parent is the canonical demonstration.

state managementdata flowpropscomponent communication
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for React interviews — assumes props and state. Difficulty: Easy

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. This is the focused technique; <a href="PASTE_PASS_DATA_URL_HERE" target="_blank" r

Solution ready — 2 min read

Classified // press E to declassify

04

Explore the playground snippets

Two inputs kept in sync by their parent, plus state moved back down
Run Playground
import { useState } from "react";

// ── Lifted: neither input owns the value. Both render what they are given. ──
function AmountInput({ label, value, onChange }) {
  return (
    <label style={{ display: "block", marginBottom: 8 }}>
      {label}:{" "}
      <input value={value} onChange={(e) => onChange(e.target.value)} style={{ width: 110 }} />
    </label>
  );
}

function Summary({ gbp, usd }) {
  return (
    <p style={{ fontSize: 14 }}>
      A sibling reading the same value: <strong>£{gbp || 0}</strong> is about{" "}
      <strong>${usd || 0}</strong>
    </p>
  );
}

const RATE = 1.27;
const round = (n) => (Number.isNaN(n) ? "" : String(Math.round(n * 100) / 100));

// ── Moved down: this owns its own state, so typing here does NOT re-render
//    the rest of the page. The mirror of lifting. ────────────────────────────
function NotesBox() {
  const [notes, setNotes] = useState("");
  return (
    <div style={{ marginTop: 12 }}>
      <textarea
        value={notes}
        onChange={(e) => setNotes(e.target.value)}
        placeholder="Notes — state lives here, not in the parent"
        rows={2}
        style={{ width: "100%" }}
      />
      <p style={{ fontSize: 12, color: "#666", margin: "4px 0 0" }}>
        {notes.length} characters — typing here re-renders only this component
      </p>
    </div>
  );
}

export default function App() {
  // The nearest common ancestor of the two inputs and the summary. Not the
  // root of some larger app — just high enough to cover everyone who needs it.
  const [amount, setAmount] = useState("100");
  const [currency, setCurrency] = useState("gbp");

  const parsed = parseFloat(amount);
  const gbp = currency === "gbp" ? amount : round(parsed / RATE);
  const usd = currency === "usd" ? amount : round(parsed * RATE);

  const handle = (which) => (next) => { setCurrency(which); setAmount(next); };

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6, maxWidth: 420 }}>
      <h4 style={{ marginTop: 0 }}>Lifted state: two inputs, one source of truth</h4>
      <AmountInput label="Pounds" value={gbp} onChange={handle("gbp")} />
      <AmountInput label="Dollars" value={usd} onChange={handle("usd")} />
      <Summary gbp={gbp} usd={usd} />

      <hr />
      <h4 style={{ margin: "0 0 4px" }}>Colocated state: kept where it is used</h4>
      <NotesBox />

      <p style={{ color: "#666", fontSize: 13 }}>
        Type in either currency box and the other follows — they never talk to
        each other, they both read from the parent. The notes box owns its own
        state, so it stays out of that entirely.
      </p>
    </div>
  );
}
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 9 of 119 decoded in the React.js track. One more won't hurt.

Back to track