Back to all notes

Colocating State: Keep State Close to Where It's Used

Reduce complexity by moving state into the component that actually needs it.

2 min read

Introduction

Colocating state means keeping state as close as possible to the component that uses it. This reduces prop drilling, keeps parents lean, and clarifies ownership.

Why this matters

When state lives higher than necessary, parents become bloated and props trickle down multiple levels. That makes components harder to read and reason about. Colocation keeps state next to the UI that owns it.

The problem

It’s tempting to keep state high up in the tree and pass it down everywhere. But when only one component actually needs that state, the parent does extra work and the child becomes less reusable.

Inefficient approach

State is lifted unnecessarily high, causing prop drilling when only one component needs it:

import { useState } from "react";

function ToggleSection({ isVisible, setIsVisible }) {
  return (
    <div>
      <button onClick={() => setIsVisible(!isVisible)}>
        {isVisible ? "Hide" : "Show"} Content
      </button>
      {isVisible && (
        <div>
          <p>This content can be toggled!</p>
        </div>
      )}
    </div>
  );
}

function App() {
  const [isVisible, setIsVisible] = useState(false);
  return (
    <div>
      <h1>Colocating State Example</h1>
      <ToggleSection isVisible={isVisible} setIsVisible={setIsVisible} />
    </div>
  );
}

export default App;

The solution

Move the state into ToggleSection. The component becomes self‑contained and easier to reuse, and the parent gets simpler by default.

import { useState } from "react";

function ToggleSection() {
  const [isVisible, setIsVisible] = useState(false);

  return (
    <div>
      <button onClick={() => setIsVisible(!isVisible)}>
        {isVisible ? "Hide" : "Show"} Content
      </button>
      {isVisible && (
        <div>
          <p>This content can be toggled!</p>
        </div>
      )}
    </div>
  );
}

function App() {
  return (
    <div>
      <h1>Colocating State Example</h1>
      <ToggleSection />
    </div>
  );
}

Step-by-step

  1. Remove isVisible state from the parent.
  2. Add const [isVisible, setIsVisible] = useState(false) inside ToggleSection.
  3. Wire the button to toggle local state.
  4. Render content conditionally based on local isVisible.
  5. Keep the parent simple—no props needed for visibility control.

When not to colocate

  • Multiple siblings need to read or update the same state. In that case, lift state up to the nearest common parent.
  • Many deep descendants need the value. Consider Context to avoid drilling.

Tips

  • Start by colocating state with the smallest component that needs it. Lift only when duplication appears.
  • Controlled components (like inputs) should manage their own transient UI state unless a parent truly needs to coordinate it.
  • Keep public interfaces small. If a child must expose control, consider a callback prop like onToggle rather than pushing all state up.

Takeaway

Colocating state cuts down prop noise and clarifies ownership. It keeps parents lean and components easy to reuse.

Knowledge base

Problem snippet:

function App() {
  const [isVisible, setIsVisible] = useState(false);
  return <ToggleSection isVisible={isVisible} setIsVisible={setIsVisible} />;
}

Solution snippet:

function ToggleSection() {
  const [isVisible, setIsVisible] = useState(false);
  // ...
}