Skip to the solution
EasyFrontend6 min read

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

813 views

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 management
  • data flow
  • props
  • component communication

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 9 sections

Try it in the playground

Open any example in the playground to run and change it.

ReactTwo inputs kept in sync by their parent, plus state moved back down
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>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 9 of 119 in React.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All React.js questions
Up nextEasy

What are the advantages of using React over plain JavaScript?

Continue