← React & TypeScript

State & Patterns

What does "lifting state up" mean in React and in what situation would you do it?

Show answer — try answering out loud first

Lifting state up is moving state from a child component to the nearest common parent, so that several components can share it. You do it when two or more sibling components need to read or modify the same data.

In React data flows downward: a parent passes information to its children via props, and one sibling can't directly read another sibling's state. When two components need to stay in sync, the solution is that neither of them owns the data: the owner becomes their nearest common ancestor. The key points:

  • The state lives in the nearest common parent, not higher than necessary.
  • The parent passes down two things: the current value and a function to update it.
  • The child that "writes" calls that function; the child that "reads" receives the value via props.
  • You end up with a single source of truth and the siblings never fall out of sync.
import { useState } from "react";

type EntradaProps = { busqueda: string; onCambio: (valor: string) => void };

// Child that WRITES: receives the value and the function to update it
function EntradaBusqueda({ busqueda, onCambio }: EntradaProps) {
  return <input value={busqueda} onChange={(e) => onCambio(e.target.value)} />;
}

// Child that READS: only needs the current value
function ListaResultados({ busqueda }: { busqueda: string }) {
  const frutas = ["manzana", "pera", "mango", "melón"];
  const filtradas = frutas.filter((fruta) => fruta.includes(busqueda));
  return (
    <ul>
      {filtradas.map((fruta) => <li key={fruta}>{fruta}</li>)}
    </ul>
  );
}

// Nearest common parent: the shared state lives here
export function Buscador() {
  const [busqueda, setBusqueda] = useState("");
  return (
    <section>
      <EntradaBusqueda busqueda={busqueda} onCambio={setBusqueda} />
      <ListaResultados busqueda={busqueda} />
    </section>
  );
}

Duplicating the state in each sibling and trying to "sync them" by hand:

// BAD: each sibling has its own copy of the data
function EntradaBusqueda() {
  const [busqueda, setBusqueda] = useState(""); // the list never sees this
  return <input value={busqueda} onChange={(e) => setBusqueda(e.target.value)} />;
}

The fix is to delete the duplicated states and declare it just once in the parent, like in the example above. If you catch yourself copying the same data into two useState, that's the sign it's time to lift the state up.

Lifting state up means moving the state to the nearest common parent when two components need to share the same data. Since data in React flows downward, one sibling can't read another's state. What I do is declare the state in the parent and pass down the value and a function to update it. That way I keep a single source of truth and the components don't fall out of sync. That said, I only lift it as high as needed, not straight to the root of the app.

Quick challenge

Create a Semaforo component with two synchronized children: Luz shows the current color and BotonCambiar advances it. The color is "verde" | "amarillo" | "rojo" and it should live in the parent.

See solution
import { useState } from "react";

type Color = "verde" | "amarillo" | "rojo";

const siguiente: Record<Color, Color> = {
  verde: "amarillo",
  amarillo: "rojo",
  rojo: "verde",
};

function Luz({ color }: { color: Color }) {
  return <p>La luz está en: {color}</p>;
}

function BotonCambiar({ onCambiar }: { onCambiar: () => void }) {
  return <button onClick={onCambiar}>Cambiar luz</button>;
}

// The parent owns the state that both children share
export function Semaforo() {
  const [color, setColor] = useState<Color>("verde");
  return (
    <div>
      <Luz color={color} />
      <BotonCambiar onCambiar={() => setColor(siguiente[color])} />
    </div>
  );
}