Lifting State Up

Share state between components by moving it to their closest common parent.

When two components need the same data, lift the state up to their nearest common parent. The parent holds the state and passes it down as props, along with callbacks to update it.

Why lift state

Keeping a single source of truth prevents two copies of the same value from drifting out of sync. Children become simpler — they just display props and report events upward.

Example

Example · javascript
import { useState } from 'react';

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

function Parent() {
  const [text, setText] = useState('');
  return (
    <div>
      <Child value={text} onChange={setText} />
      <Child value={text} onChange={setText} />
      <p>Shared value: {text}</p>
    </div>
  );
}

When to use it

  • A temperature converter lifts the shared numeric value to the parent so both a Celsius and a Fahrenheit input always reflect the same measurement.
  • A product filtering UI lifts the active filter criteria to the parent so the FilterBar and ProductGrid components both read from the same source of truth.
  • A wizard form lifts each step's validated data to the top-level Wizard component so the final summary page can access every field without prop drilling through intermediaries.

More examples

Lift state to share between siblings

Moves shared state to the parent and passes it down with a callback so two sibling inputs stay in sync.

Example · jsx
import { useState } from 'react';

function Parent() {
  const [value, setValue] = useState('');
  return (
    <>
      <InputA value={value} onChange={setValue} />
      <InputB value={value} onChange={setValue} />
    </>
  );
}

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

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

Filter bar lifting criteria

Lifts the search string into a parent so SearchBar updates it and ProductGrid derives filtered results from it.

Example · jsx
import { useState } from 'react';

function ProductPage({ products }) {
  const [search, setSearch] = useState('');
  const filtered = products.filter(p =>
    p.name.toLowerCase().includes(search.toLowerCase())
  );
  return (
    <>
      <SearchBar query={search} onSearch={setSearch} />
      <ProductGrid items={filtered} />
    </>
  );
}

Callback to update parent state

Passes a parent-owned updater through two levels of props so the deep QuantitySelector can modify cart state.

Example · jsx
function QuantitySelector({ quantity, onQuantityChange }) {
  return (
    <div>
      <button onClick={() => onQuantityChange(quantity - 1)}>-</button>
      <span>{quantity}</span>
      <button onClick={() => onQuantityChange(quantity + 1)}>+</button>
    </div>
  );
}

function CartItem({ item, updateQty }) {
  return (
    <div>
      <span>{item.name}</span>
      <QuantitySelector
        quantity={item.qty}
        onQuantityChange={qty => updateQty(item.id, qty)}
      />
    </div>
  );
}

Discussion

  • Be the first to comment on this lesson.