ToolPopToolPop
React · Lesson 6 of 8

Lifting state and Context, where does this data live?

12 min readUpdated 24 Jun 2026

Two components need the same state. Where does the state live? React's answer is older than hooks: it lives in the nearest common parent. That is "lifting state up".

When that parent is six levels above, you start passing props through components that do not care. That pain has a name: prop drilling. Context is the cure, but only sometimes.

Diagram
rendering diagram...
Prop drilling vs Context. Two ways to share state down a tree.

Lifting state, the normal way

Two sibling components, one source of truth.

jsx
function App() {
  const [city, setCity] = useState('Bengaluru');
  return (
    <>
      <CitySelector value={city} onChange={setCity} />
      <RestaurantList city={city} />
    </>
  );
}

CitySelector writes. RestaurantList reads. The parent holds the state and passes it down. Boring, predictable, correct.

Prop drilling, the pain

Now RestaurantList lives five layers deep, inside <Page> > <Main> > <Feed> > <Section> > <List>. Every layer in between has to forward city even though it does not care.

  • Three layers: annoying, fine.
  • Five layers: time to think about it.
  • Eight layers: you have an architecture smell, not just a prop problem.

Context, the lift you actually need

Context lets any descendant grab a value without props. Two halves: a provider that sets the value, a hook that reads it.

jsx
const ThemeContext = createContext('light');
 
function App() {
  const [theme, setTheme] = useState('dark');
  return (
    <ThemeContext.Provider value={theme}>
      <Page />
    </ThemeContext.Provider>
  );
}
 
function Button() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Order</button>;
}

Button reads theme without anybody passing it down. Beautiful when you mean it.

When Context is right

  • Theme (dark or light).
  • Current user / auth.
  • Locale / language.
  • App-wide feature flags.

Notice the pattern: things that almost everything reads, that almost never change.

When Context is overkill

  • Form state. Use react-hook-form or local state.
  • Server data. Use TanStack Query or SWR.
  • Anything that changes 60 times a second. Animations, drag state.
  • Three siblings sharing one value. Just lift to their parent.

Context is not a state manager. It is a way to pass a value through the tree. If you are reaching for Context to avoid lifting state twice, you are over-engineering.

The performance gotcha

Every consumer of a Context re-renders when the Context value changes. If you do this:

jsx
<UserContext.Provider value={{ user, setUser }}>

You create a brand new object every render. Every consumer re-renders every time, even if user did not actually change. The fix:

  • Memoize the value: const value = useMemo(() => ({ user, setUser }), [user]);
  • Split contexts: one for user (rarely changes), one for setUser (never changes), one for transient stuff.

When to skip ahead to Zustand or Jotai

If you have multiple independent slices of global state, complex updates, or selector-style reads, a tiny store library (Zustand is the popular pick in 2026) saves you from Context gymnastics. Reach for it when Context feels like the wrong shape, not before.

Next: fetching data, which is most of what real apps actually do.

Free tools you can use while you learn

Chai0/1 done

Watching quietly. Tap me if you want a tip.

React Playground
preview

Try this (0 of 1 done)

  1. 1

    Add a "clear" button that sets the text back to empty.

    show answer
    function App() {
      const [text, setText] = React.useState('');
      return (
        <div>
          <Input value={text} onChange={setText} />
          <Display value={text} />
          <button onClick={() => setText('')}>clear</button>
        </div>
      );
    }