React Essentials — Components, JSX, Props, State & MoreEntry 8 of 11
Lifting State Up
Moving state to the closest ancestor component that contains every component needing that state.
Notes
When two sibling components need the same state, neither can own it, because props only flow down. Lift the state up to the closest ancestor component that has access to all components that need to work with that state. The ancestor then:
- Manages the state with
useState. - Passes the state value down via props to the children that need to read it.
- Passes a function down via props that eventually changes the state. This lets a child initiate the state change without owning the state.
This builds directly on Entry 3 (passing functions as props). Lift only as high as necessary; state placed too high causes unnecessary re-renders and prop drilling.
What / Why / When
- What
Relocating state to a shared parent.
- Why
Keeps a single source of truth so siblings never fall out of sync.
- When
Two or more components need to read or change the same data.
- Use case
In a tic-tac-toe game, the game board changes whose turn it is, and the player list highlights the active player. Both need
activePlayer, so it lives in their parent,App.
Code compare
import { useState } from "react";type PlayerSymbol = "X" | "O";// Ancestor: owns the stateexport function App() { const [activePlayer, setActivePlayer] = useState<PlayerSymbol>("X"); function handleSelectSquare() { setActivePlayer((current) => (current === "X" ? "O" : "X")); } return ( <main> <ol> <Player symbol="X" isActive={activePlayer === "X"} /> <Player symbol="O" isActive={activePlayer === "O"} /> </ol> <GameBoard activePlayerSymbol={activePlayer} onSelectSquare={handleSelectSquare} /> </main> );}// Child 1: reads the statefunction Player({ symbol, isActive }: { symbol: PlayerSymbol; isActive: boolean }) { return <li className={isActive ? "active" : undefined}>{symbol}</li>;}// Child 2: reads the state and initiates changestype GameBoardProps = { activePlayerSymbol: PlayerSymbol; onSelectSquare: () => void;};function GameBoard({ activePlayerSymbol, onSelectSquare }: GameBoardProps) { return <button onClick={onSelectSquare}>Place {activePlayerSymbol}</button>;}The same pattern: the parent owns a signal, passes the value down with input(), and listens for changes with output().
import { Component, input, output, signal } from "@angular/core";type PlayerSymbol = "X" | "O";// Child 1: reads the state@Component({ selector: "app-player", template: `<li [class.active]="isActive()">{{ symbol() }}</li>`,})export class PlayerComponent { symbol = input.required<PlayerSymbol>(); isActive = input(false);}// Child 2: reads the state and initiates changes@Component({ selector: "app-game-board", template: `<button (click)="squareSelected.emit()">Place {{ activePlayerSymbol() }}</button>`,})export class GameBoardComponent { activePlayerSymbol = input.required<PlayerSymbol>(); squareSelected = output<void>();}// Ancestor: owns the state@Component({ selector: "app-root", imports: [PlayerComponent, GameBoardComponent], template: ` <main> <ol> <app-player symbol="X" [isActive]="activePlayer() === 'X'" /> <app-player symbol="O" [isActive]="activePlayer() === 'O'" /> </ol> <app-game-board [activePlayerSymbol]="activePlayer()" (squareSelected)="handleSelectSquare()" /> </main> `,})export class AppComponent { activePlayer = signal<PlayerSymbol>("X"); handleSelectSquare() { this.activePlayer.update((current) => (current === "X" ? "O" : "X")); }}Differences and gotchas
- The pattern is identical in both frameworks. Only the child-to-parent mechanism differs: React passes a callback prop down, Angular emits an
output()up. - When state must be shared across many levels, Angular developers usually move it into an injectable service holding a signal. React's equivalent is Context (or a store such as Redux Toolkit), covered in later chapters.
- In React, lifting state causes the ancestor and all its children to re-render. In Angular, only bindings that read the signal update.
Quick reference
| Step | React | Angular |
|---|---|---|
| Own the state | useState in the ancestor | signal() in the ancestor |
| Pass the value down | Props | input() |
| Let a child change it | Callback prop (onSelectSquare) | output() + (squareSelected) |
| Share widely | Context / Redux Toolkit | Injectable service with a signal |