# Application State Management

Course: [React](https://www.devtutorials.ai/learn/react) · Section: State Sharing
Source: https://www.devtutorials.ai/learn/react/state-sharing/application-state-management

> Shared state, state ownership, lifting state up, managing shared state, keeping parent and child in sync, where to place state, local vs global state, and choosing an approach.

## Introduction to Shared State

**Shared state** is state that more than one component needs. For example, the number of books in the cart is shown in the header **and** on the cart page.

If each component keeps its own copy, the copies can show different numbers. To keep them the same, you keep **one** copy and share it.

### Real-World Example

In a bookshop app, the header shows the cart count, the book page adds items, and the checkout page shows the total. All three read the same cart.

The cart is kept in one place: a `CartProvider` with `useReducer`. Each component sends actions such as `{ type: "add", book }`, and every part of the page shows the same cart.

<figure className="react-fig">
<svg viewBox="0 0 520 190" role="img" aria-labelledby="fig-s-sib" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-s-sib">Left: two siblings each keep their own count, so they disagree. Right: the parent keeps one count and passes it to both, so they agree</title>
  <text x="130" y="18" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>Each child has its own state</text>
  <rect x="80" y="30" width="100" height="40" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="130.0" y="55.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>App</text>
  <path d="M110 70 L70 110" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M70 110 L73.4 101.7 L78.3 106.6 Z" style={{ fill: "var(--faint)" }} />
  <path d="M150 70 L190 110" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M190 110 L181.7 106.6 L186.6 101.7 Z" style={{ fill: "var(--faint)" }} />
  <rect x="15" y="112" width="110" height="52" rx="8" style={{ fill: "var(--bad-soft)", stroke: "var(--bad)", strokeWidth: 1.5 }} />
  <text x="70.0" y="135.0" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Header</text>
  <text x="70.0" y="152.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>count = 2</text>
  <rect x="140" y="112" width="110" height="52" rx="8" style={{ fill: "var(--bad-soft)", stroke: "var(--bad)", strokeWidth: 1.5 }} />
  <text x="195.0" y="135.0" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>CartPage</text>
  <text x="195.0" y="152.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>count = 5</text>
  <text x="130" y="182" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>Different numbers</text>
  <text x="390" y="18" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 12, fontWeight: 700, fontFamily: "var(--sans)" }}>The parent has the state</text>
  <rect x="330" y="30" width="120" height="40" rx="8" style={{ fill: "var(--ok-soft)", stroke: "var(--ok)", strokeWidth: 1.5 }} />
  <text x="390.0" y="47.0" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>App</text>
  <text x="390.0" y="64.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>count = 5</text>
  <path d="M370 70 L330 110" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M330 110 L333.4 101.7 L338.3 106.6 Z" style={{ fill: "var(--faint)" }} />
  <text x="350.0" y="88" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontFamily: "var(--mono)" }}>5</text>
  <path d="M410 70 L450 110" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M450 110 L441.7 106.6 L446.6 101.7 Z" style={{ fill: "var(--faint)" }} />
  <text x="430.0" y="88" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontFamily: "var(--mono)" }}>5</text>
  <rect x="270" y="112" width="110" height="52" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="325.0" y="135.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Header</text>
  <text x="325.0" y="152.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>shows 5</text>
  <rect x="395" y="112" width="110" height="52" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="450.0" y="135.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>CartPage</text>
  <text x="450.0" y="152.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>shows 5</text>
  <text x="390" y="182" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>Same number</text>
  <circle r="6" style={{ fill: "var(--ok)" }}>
    <animateMotion dur="2s" repeatCount="indefinite" path="M370 70 L330 110 M410 70 L450 110" />
  </circle>
</svg>
<figcaption>When two components must show the same data, keep one copy of it in their parent.</figcaption>
</figure>

If two components must always show the same data, they must not each keep their own copy. Keep one copy, in one place.

## State Ownership

Every piece of state has an **owner**: the one component that creates it with `useState`. Only the owner can change it.

Other components get the state from the owner:

- **To read it**, they receive it as a **prop**.
- **To change it**, they receive a **function** from the owner and call it.

This keeps one clear rule: there is one place where the data lives, and one place where it changes.

The lab below shows what goes wrong without a shared owner. Each component owns its own `count`.

    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

Click the button a few times. The button's number goes up, but the badge stays at 0. Each component owns its own `count`, so they do not know about each other.

## Lifting State Up

**Lifting state up** means moving the state from a child up to its parent. The parent becomes the owner. Then it passes the state down to every child that needs it.

There are three steps:

1. **Move** the `useState` line from the child up to the parent.
2. **Pass the value down** to the children as a prop.
3. **Pass a function down** to the child that changes it.

<figure className="react-fig">
<svg viewBox="0 0 520 170" role="img" aria-labelledby="fig-s-lift" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-s-lift">Lifting state up: the state moves from a child up to the parent</title>
  <rect x="190" y="10" width="140" height="46" rx="8" style={{ fill: "var(--ok-soft)", stroke: "var(--ok)", strokeWidth: 1.5 }} />
  <text x="260.0" y="30.0" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>App (parent)</text>
  <text x="260.0" y="47.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>new home of the state</text>
  <path d="M250 56 L140 104" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M140 104 L146.2 97.5 L149.0 103.9 Z" style={{ fill: "var(--faint)" }} />
  <path d="M270 56 L380 104" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M380 104 L371.0 103.9 L373.8 97.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="70" y="106" width="140" height="52" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="140.0" y="129.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>LikeButton</text>
  <text x="140.0" y="146.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}> </text>
  <rect x="310" y="106" width="140" height="46" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="380.0" y="134.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>LikeCount</text>
  <g>
    <rect x="105" y="134" width="70" height="20" rx="11" style={{ fill: "var(--accent-secondary)" }} />
    <text x="140" y="148" textAnchor="middle" style={{ fill: "#fff", fontSize: 11, fontWeight: 700, fontFamily: "var(--mono)" }}>likes</text>
    <animateTransform attributeName="transform" type="translate" values="0 0; 120 -100; 120 -100; 0 0" keyTimes="0; 0.4; 0.85; 1" dur="4s" repeatCount="indefinite" />
  </g>
</svg>
<figcaption>The likes state moves up from a child to the parent. Now both children can use it.</figcaption>
</figure>

  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

Click the button. Now the badge changes too. `App` owns `count`. `CartBadge` only shows it, and `AddButton` only asks `App` to change it.

Neither child has `useState` any more. They are now simple components that get everything from props.

## Shared State Management

**Shared state management** means deciding how shared state is stored, read and changed. When many children use the same state, keep these points in mind:

- **One owner** — Only one component has the `useState` for it.
- **Read with props** — Children receive the value as a prop.
- **Change with functions** — Children receive functions such as `onAdd` or `onRemove`. They never change the value themselves.
- **Clear names** — Name function props after the action: `onAdd`, `onRemove`, `onClear`.

      <h3>Book Corner · cart: {cart.length}</h3>
    </div>
  );
}
`,
    "BookList.tsx": `const books = ["The Alchemist", "Wings of Fire", "Atomic Habits"];

interface BookListProps {
  onAdd: (title: string) => void;
}

  return (
    <ul>
      {books.map((b) => (
        <li key={b}>
          {b} <button onClick={() => onAdd(b)}>Add</button>
        </li>
      ))}
    </ul>
  );
}
`,
    "CartSummary.tsx": `interface CartSummaryProps {
  cart: string[];
  onClear: () => void;
}

  if (cart.length === 0) return <p>Your cart is empty.</p>;
  return (
    <div>
      <p>In your cart: {cart.join(", ")}</p>
      <button onClick={onClear}>Clear cart</button>
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

`App` owns the cart. The heading, `BookList` and `CartSummary` all show the same cart, because there is only one.

## Parent-Child State Synchronization

**Synchronization** means keeping two things the same. A parent and a child stay in sync when:

- the parent sends the current value **down** as a prop, and
- the child sends changes **up** by calling a function prop.

This is often used for inputs. The parent owns the text, and the child shows the input box.

<figure className="react-fig">
<svg viewBox="0 0 520 170" role="img" aria-labelledby="fig-s-sync" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-s-sync">Parent and child stay in sync: data goes down as props, changes go up through a function prop</title>
  <rect x="170" y="10" width="180" height="48" rx="8" style={{ fill: "var(--ok-soft)", stroke: "var(--ok)", strokeWidth: 1.5 }} />
  <text x="260.0" y="31.0" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>App</text>
  <text x="260.0" y="48.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>search = "hab"</text>
  <rect x="170" y="112" width="180" height="48" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="260.0" y="133.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>SearchBox</text>
  <text x="260.0" y="150.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>&lt;input /&gt;</text>
  <path d="M230 58 L230 108" style={{ stroke: "var(--accent)", strokeWidth: 2, fill: "none" }} />
  <path d="M290 108 L290 62" style={{ stroke: "var(--accent-secondary)", strokeWidth: 2, fill: "none" }} />
  <text x="215" y="88" textAnchor="end" style={{ fill: "var(--accent)", fontSize: 11, fontWeight: 600, fontFamily: "var(--sans)" }}>value (prop)</text>
  <text x="305" y="88" textAnchor="start" style={{ fill: "var(--accent-secondary)", fontSize: 11, fontWeight: 600, fontFamily: "var(--sans)" }}>onSearch (function)</text>
  <circle r="6" style={{ fill: "var(--accent)" }}>
    <animateMotion dur="1.6s" repeatCount="indefinite" path="M230 58 L230 108" />
  </circle>
  <circle r="6" style={{ fill: "var(--accent-secondary)" }}>
    <animateMotion dur="1.6s" repeatCount="indefinite" path="M290 108 L290 58" />
  </circle>
</svg>
<figcaption>Data flows down as props. Changes flow up when the child calls a function from the parent.</figcaption>
</figure>

  );
}
`,
    "App.tsx": `import { useState } from "react";

const books = ["The Alchemist", "Wings of Fire", "Atomic Habits", "Ikigai"];

  const [search, setSearch] = useState("");
  const results = books.filter((b) => b.toLowerCase().includes(search.toLowerCase()));

  return (
    <div>
      <p>{results.length} result(s) for "{search}"</p>
      <ul>
        {results.map((b) => <li key={b}>{b}</li>)}
      </ul>
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

Type in the box. The list filters as you type. Now click **Clear**. The parent sets `search` to `""`, and the box inside the child becomes empty too. Parent and child always show the same text.

Look at this line in `App`:

```tsx
<SearchBox value={search} onSearch={setSearch} />
```

`value={search}` sends the text down. `onSearch={setSearch}` gives the child a way to change it.

## State Placement Strategy

Where should a piece of state live? Follow these steps:

1. **List every component** that reads or changes the state.
2. **Find their closest shared parent** in the component tree.
3. **Put the state there.** If only one component needs it, keep it in that component.

<figure className="react-fig">
<svg viewBox="0 0 520 190" role="img" aria-labelledby="fig-s-place" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-s-place">State placement: find the closest parent shared by every component that needs the state</title>
  <rect x="200" y="10" width="120" height="40" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="260.0" y="35.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>App</text>
  <path d="M240 50 L150 82" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M150 82 L156.6 75.9 L159.0 82.5 Z" style={{ fill: "var(--faint)" }} />
  <path d="M280 50 L370 82" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M370 82 L361.0 82.5 L363.4 75.9 Z" style={{ fill: "var(--faint)" }} />
  <rect x="90" y="84" width="120" height="40" rx="8" style={{ fill: "var(--ok-soft)", stroke: "var(--ok)", strokeWidth: 1.5 }} />
  <text x="150.0" y="101.0" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Shop</text>
  <text x="150.0" y="118.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>cart lives here</text>
  <rect x="310" y="84" width="120" height="40" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="370.0" y="101.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Footer</text>
  <text x="370.0" y="118.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>does not need it</text>
  <path d="M130 124 L80 150" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M80 150 L85.7 143.1 L89.0 149.3 Z" style={{ fill: "var(--faint)" }} />
  <path d="M170 124 L220 150" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M220 150 L211.0 149.3 L214.3 143.1 Z" style={{ fill: "var(--faint)" }} />
  <rect x="20" y="152" width="120" height="34" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="80.0" y="166.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Header</text>
  <text x="80.0" y="183.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>needs cart</text>
  <rect x="160" y="152" width="120" height="34" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="220.0" y="166.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>BookList</text>
  <text x="220.0" y="183.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>needs cart</text>
  <rect x="84" y="78" width="132" height="52" rx="12" style={{ fill: "none", stroke: "var(--ok)", strokeWidth: 2, strokeDasharray: "5 4" }}>
    <animate attributeName="opacity" values="0.2;1;0.2" dur="2s" repeatCount="indefinite" />
  </rect>
</svg>
<figcaption>Header and BookList need the cart. Their closest shared parent is Shop, so the cart state goes there.</figcaption>
</figure>

Do not put the state higher than it needs to be. If you put everything in `App`, then every change re-renders the whole app, and `App` becomes very long.

The lab below places each piece of state in the right spot:

- `isOpen` is used only by `Menu`, so it stays **local** inside `Menu`.
- `cart` is used by `Header` and `BookList`, so it lives in `Shop`, their closest shared parent.

      <button onClick={() => setIsOpen(!isOpen)}>Menu</button>
      {isOpen && <ul><li>Offers</li><li>Help</li></ul>}
    </div>
  );
}

function Header({ count }: { count: number }) {
  return <h3>Book Corner · cart: {count}</h3>;
}

function BookList({ onAdd }: { onAdd: (title: string) => void }) {
  return (
    <ul>
      <li>Ikigai <button onClick={() => onAdd("Ikigai")}>Add</button></li>
      <li>Sapiens <button onClick={() => onAdd("Sapiens")}>Add</button></li>
    </ul>
  );
}

function Shop() {
  const [cart, setCart] = useState<string[]>([]); // Header and BookList need this
  return (
    <div>
      <BookList onAdd={(title) => setCart([...cart, title])} />
    </div>
  );
}

  return (
    <div>
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

createRoot(document.getElementById("root")!).render(<App />);
`,
  }}
/>

Open the menu and add books. Opening the menu does not touch the cart, and adding a book does not touch the menu. Each state lives only where it is needed.

Keep state as low as possible, but as high as needed. Start in the component that uses it. Lift it up only when another component needs it too.

## Local State Compared with Global State

- **Local state** belongs to one component, or to a small group with a shared parent. Examples: the text in a search box, whether a menu is open.
- **Global state** is used by many components all over the app. Examples: the logged-in user, the theme, the language, the cart.

<figure className="react-fig">
<svg viewBox="0 0 520 170" role="img" aria-labelledby="fig-s-lg" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-s-lg">Local state lives inside one component. Global state is shared with many components across the app</title>
  <text x="120" y="18" textAnchor="middle" style={{ fill: "var(--accent)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Local state</text>
  <rect x="40" y="32" width="160" height="60" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="120.0" y="59.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>SearchBox</text>
  <text x="120.0" y="76.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}> </text>
  <rect x="70" y="66" width="100" height="20" rx="10" style={{ fill: "var(--accent)" }} />
  <text x="120" y="80" textAnchor="middle" style={{ fill: "#fff", fontSize: 11, fontFamily: "var(--mono)" }}>text = "hab"</text>
  <text x="120" y="115" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>used by one component</text>
  <text x="390" y="18" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Global state</text>
  <rect x="320" y="30" width="140" height="40" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="390.0" y="47.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>user = Asha</text>
  <text x="390.0" y="64.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>shared by many</text>
  <path d="M350 70 L300 124" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M300 124 L303.1 115.5 L308.2 120.3 Z" style={{ fill: "var(--faint)" }} />
  <path d="M390 70 L390 124" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M390 124 L386.5 115.7 L393.5 115.7 Z" style={{ fill: "var(--faint)" }} />
  <path d="M430 70 L480 124" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M480 124 L471.8 120.3 L476.9 115.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="250" y="126" width="90" height="32" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="295.0" y="147.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Header</text>
  <rect x="345" y="126" width="90" height="32" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="390.0" y="147.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Cart</text>
  <rect x="440" y="126" width="75" height="32" rx="8" style={{ fill: "var(--panel2)", stroke: "var(--faint)", strokeWidth: 1.5 }} />
  <text x="477.5" y="147.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Profile</text>
  <circle r="6" style={{ fill: "var(--accent-secondary)" }}>
    <animateMotion dur="2.4s" repeatCount="indefinite" path="M350 70 L300 124 M390 70 L390 124 M430 70 L480 124" />
  </circle>
</svg>
<figcaption>Keep state local when only one component needs it. Make it global only when many parts of the app need it.</figcaption>
</figure>

<div className="tscroll">

| | Local state | Global state |
|---|---|---|
| Who uses it | One component, or a small group | Many components across the app |
| Where it lives | Inside the component, or its parent | Near the top of the app |
| How it is shared | Props | Context (next lesson), or a state library |
| Examples | Input text, open/closed, selected tab | Logged-in user, theme, language, cart |

</div>

Most state in an app is local. Only a few things are truly global.

## Choosing a State Management Approach

Pick the simplest approach that works. Move to the next one only when you have a real problem.

<div className="tscroll">

| Situation | Approach |
|---|---|
| Only one component uses the state | `useState` in that component |
| A few nearby components need it | Lift it up to their shared parent, and pass props |
| The state has many kinds of changes | `useReducer` in the owner |
| Many components far apart need it | **Context** (next lesson) |
| A very big app with lots of shared data | A state library, such as Redux Toolkit or Zustand |

</div>

Most apps need only `useState`, lifting state up, and a little context. Reach for a state library only when the app is large and these are not enough.

## Common mistakes

<div className="tscroll">

| Mistake | What you see | Fix |
|---|---|---|
| Two components each keep their own copy of the same data | They show different values | Lift the state to their shared parent |
| A child changes a prop directly | Error, or the change is lost | Call a function prop from the parent |
| Putting all state in `App` | `App` is huge, and every change re-renders everything | Keep state in the lowest shared parent |
| Copying a prop into the child's own state | The child stops updating when the parent changes | Use the prop directly |
| Passing `setCount(count + 1)` instead of a function | It runs while rendering, and loops | Pass `() => setCount(count + 1)` |
| Using global state for everything | The code is hard to follow | Keep most state local |

</div>

## Check Yourself

## Exercises

## What to Remember

- When two components need the same data, keep one copy in their closest shared parent. This is lifting state up.
- The owner passes the value down as props, and passes functions down so children can ask for changes.
- Keep state as low as possible. Most state is local. Use context only for truly global data.

Next: Context API
