# Component Lifecycle

Course: [React](https://www.devtutorials.ai/learn/react) · Section: React Hooks
Source: https://www.devtutorials.ai/learn/react/hooks/component-lifecycle

> Mounting, updating and unmounting, how rendering and effects fit together, managing the lifecycle with useEffect, effect dependencies and dependency arrays, cleanup functions, and avoiding infinite effect loops.

## Introduction to Component Lifecycle

The **lifecycle** of a component is the set of stages it goes through: it appears on the screen, it changes, and it is removed from the screen.

Knowing the lifecycle tells you **when** your code runs. For example: load data when the component appears, and stop a timer when it is removed.

### Real-World Example

A bookshop's `OrderTracker` component appears when the user opens an order. In `useEffect`, it starts a timer that asks the server for the delivery status every 10 seconds.

When the user leaves the page, React removes the component and runs the cleanup function, which stops the timer. Without the cleanup, the requests would continue in the background.

Every component has three stages: **mounting** (appears), **updating** (changes), and **unmounting** (is removed).

## Lifecycle Phases and React Rendering

The three stages are called **phases**.

<figure className="react-fig">
<svg viewBox="0 0 520 170" role="img" aria-labelledby="fig-life-title" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-life-title">Lifecycle: Mounting, then Updating (can repeat many times), then Unmounting</title>
  <g style={{ fontFamily: "var(--sans)" }}>
    <rect x="10" y="50" width="140" height="64" rx="10" style={{ fill: "var(--ok-soft)", stroke: "var(--ok)", strokeWidth: 1.5 }} />
    <text x="80" y="78" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 15, fontWeight: 700 }}>Mounting</text>
    <text x="80" y="98" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 12 }}>appears on screen</text>
    <rect x="190" y="50" width="140" height="64" rx="10" style={{ fill: "var(--accent-soft)", stroke: "var(--accent)", strokeWidth: 1.5 }} />
    <text x="260" y="78" textAnchor="middle" style={{ fill: "var(--accent)", fontSize: 15, fontWeight: 700 }}>Updating</text>
    <text x="260" y="98" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 12 }}>state or props change</text>
    <rect x="370" y="50" width="140" height="64" rx="10" style={{ fill: "var(--bad-soft)", stroke: "var(--bad)", strokeWidth: 1.5 }} />
    <text x="440" y="78" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 15, fontWeight: 700 }}>Unmounting</text>
    <text x="440" y="98" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 12 }}>removed from screen</text>
    <text x="260" y="154" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 12 }}>can happen many times</text>
  </g>
  <g style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }}>
    <path d="M150 82 L186 82" /><path d="M330 82 L366 82" />
    <path d="M230 114 Q260 150 290 114" />
  </g>
  <path d="M284 118 L292 112 L294 122 Z" style={{ fill: "var(--faint)" }} />
  <circle r="7" style={{ fill: "var(--accent-secondary)" }}>
    <animateMotion dur="5s" repeatCount="indefinite" path="M80 82 L260 82 Q260 82 230 114 Q260 150 290 114 L260 82 L440 82" />
  </circle>
</svg>
<figcaption>A component mounts once, may update many times, and unmounts once.</figcaption>
</figure>

Each time a component goes through a phase, React **renders** it: it runs the component function to get the new TSX. Then React updates the screen. After the screen is updated, React runs your **effects**.

<figure className="react-fig">
<svg viewBox="0 0 520 64" role="img" aria-labelledby="fig-l-phases" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-l-phases">Every phase: React renders, updates the screen, then runs effects</title>
  <rect x="10" y="10" width="140" height="44" rx="8" style={{ fill: "var(--accent-soft)", stroke: "var(--accent)", strokeWidth: 1.5 }} />
  <text x="80.0" y="29.0" textAnchor="middle" style={{ fill: "var(--accent)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>1. Render</text>
  <text x="80.0" y="46.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>run the component</text>
  <path d="M150 32 L186 32" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M186 32 L177.7 35.5 L177.7 28.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="190" y="10" width="140" height="44" rx="8" style={{ fill: "var(--ok-soft)", stroke: "var(--ok)", strokeWidth: 1.5 }} />
  <text x="260.0" y="29.0" textAnchor="middle" style={{ fill: "var(--ok)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>2. Update screen</text>
  <text x="260.0" y="46.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>change the page</text>
  <path d="M330 32 L366 32" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M366 32 L357.7 35.5 L357.7 28.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="370" y="10" width="140" height="44" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="440.0" y="29.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>3. Run effects</text>
  <text x="440.0" y="46.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>useEffect code</text>
  <circle r="6" style={{ fill: "var(--accent-secondary)" }}>
    <animateMotion dur="3s" repeatCount="indefinite" path="M80 32 L186 32 L366 32 L440 32" />
  </circle>
</svg>
<figcaption>Effects always run last, after the screen is updated.</figcaption>
</figure>

- **Render** — React runs your component function. Do not do side work here, only work out the TSX.
- **Update the screen** — React changes the page to match.
- **Run effects** — React runs your `useEffect` code. This is where side work goes, such as timers and loading data.

## Mounting Phase

**Mounting** is when a component appears on the screen for the first time. It happens once.

In the mounting phase:

1. React runs the component function. State gets its starting values.
2. React puts the TSX on the screen.
3. React runs every `useEffect` in the component.

To run code **only once**, when the component mounts, use an effect with an **empty** dependency array `[]`.

```tsx
useEffect(() => {
  console.log("Shop opened");
}, []); // [] means: only after mounting
```

## Updating Phase

**Updating** happens when the component's state or props change. React renders the component again and updates the screen. This can happen many times.

To run code after an update, put the value you care about in the dependency array. The effect then runs after mounting, and again each time that value changes.

```tsx
useEffect(() => {
  console.log("Cart now has " + count + " books");
}, [count]); // runs when count changes
```

## Unmounting Phase

**Unmounting** is when a component is removed from the screen. For example, a pop-up is closed, or the user goes to another page.

Before it is removed, a component should **clean up** anything it started: stop timers, stop listening to events, and cancel work that is still running. You do this with a **cleanup function**, which you return from the effect.

```tsx
useEffect(() => {
  console.log("Shop opened");
  return () => {
    console.log("Shop closed"); // runs when the component is removed
  };
}, []);
```

## Lifecycle Management with useEffect

In function components, `useEffect` handles all three phases. You choose the phase with the **dependency array** and the **cleanup function**.

<div className="tscroll">

| You want to run code... | Write |
|---|---|
| Once, when the component mounts | `useEffect(() => { ... }, [])` |
| When a value changes (and on mount) | `useEffect(() => { ... }, [value])` |
| After every render | `useEffect(() => { ... })` (no array) |
| When the component unmounts | `return () => { ... }` inside an effect with `[]` |

</div>

## Component Lifecycle Flow with Examples

The lab below logs every phase. `BookPanel` has three effects: one for mount and unmount, one for updates, and one that runs after every render.

      <h3>Atomic Habits</h3>
      <button onClick={() => setLikes(likes + 1)}>Like ({likes})</button>
    </div>
  );
}
`,
    "App.tsx": `import { useState } from "react";

  const [show, setShow] = useState(true);

  return (
    <div>
      <button onClick={() => setShow(!show)}>{show ? "Hide panel" : "Show panel"}</button>
      {show && <BookPanel />}
    </div>
  );
}
`,
    "App.css": `.panel { border: 1px solid #cbd5e1; border-radius: 8px; padding: 4px 12px 12px; margin-top: 8px; }
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

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

Read the console as you try each step:

1. **On load:** "Render", then "Mounted", then "Effect: likes is now 0".
2. **Click Like:** "Render: likes = 1", then "Effect: likes is now 1". The mount effect does **not** run again.
3. **Click Hide panel:** "Unmounted: BookPanel removed". The cleanup function ran.
4. **Click Show panel:** the panel mounts again, from the start, with `likes = 0`.

"Render" always comes before the effects. React first works out the TSX and updates the screen. Only then does it run your effects.

## Common Lifecycle Scenarios

Most effects you write will follow one of these patterns.

### Scenario 1: Load Data When the Component Mounts

```tsx
useEffect(() => {
  fetchBooks().then((list) => setBooks(list));
}, []);
```

### Scenario 2: Run Again When a Value Changes

When the user picks a new category, load the books for that category.

```tsx
useEffect(() => {
  fetchBooks(category).then((list) => setBooks(list));
}, [category]);
```

### Scenario 3: Start Something, and Stop It on Unmount

Start a timer, an event listener or a connection, and stop it in the cleanup.

```tsx
useEffect(() => {
  const id = setInterval(() => setSeconds((s) => s + 1), 1000);
  return () => clearInterval(id);
}, []);
```

### Scenario 4: Keep Something Outside React in Step with State

```tsx
useEffect(() => {
  document.title = count + " books in cart";
}, [count]);
```

## Effect Dependencies

A **dependency** is a value from your component that the effect uses, such as a state, a prop, or a variable made from them.

**Every value the effect uses should be in the dependency array.** Then React knows to run the effect again when one of them changes.

```tsx
useEffect(() => {
  console.log("Showing " + category + " books, page " + page);
}, [category, page]); // both values are used, so both are listed
```

If you leave one out, the effect uses an **old** value of it. This is a common bug.

      <button onClick={() => setCategory("Fiction")}>Fiction</button>{" "}
      <button onClick={() => setCategory("Biography")}>Biography</button>{" "}
      <button onClick={() => setCategory("Science")}>Science</button>
      <p>Wrong (missing dependency): {wrongTitle}</p>
      <p>Right (category listed): {rightTitle}</p>
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

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

Click **Biography**. The "Right" line changes. The "Wrong" line still says "Fiction", because its effect ran only once, on mount.

## Dependency Arrays

The **dependency array** is the second input to `useEffect`. It decides **when** the effect runs. There are three forms.

<figure className="react-fig">
<svg viewBox="0 0 520 150" role="img" aria-labelledby="fig-l-deps" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-l-deps">Which renders run the effect: no array runs every time, empty array only the first, [likes] the first and when likes changes</title>
  <text x="230" y="18" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>mount</text>
  <text x="300" y="18" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>likes</text>
  <text x="370" y="18" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>views</text>
  <text x="440" y="18" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>likes</text>
  <text x="335" y="34" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 10, fontWeight: 400, fontFamily: "var(--sans)" }}>what changed in each render</text>
  <text x="10" y="65" textAnchor="start" style={{ fill: "var(--ink2)", fontSize: 12, fontWeight: 600, fontFamily: "var(--mono)" }}>useEffect(fn)</text>
  <circle cx="230" cy="60" r="9" style={{ fill: "var(--ok)" }} />
  <circle cx="300" cy="60" r="9" style={{ fill: "var(--ok)" }} />
  <circle cx="370" cy="60" r="9" style={{ fill: "var(--ok)" }} />
  <circle cx="440" cy="60" r="9" style={{ fill: "var(--ok)" }} />
  <text x="10" y="100" textAnchor="start" style={{ fill: "var(--ink2)", fontSize: 12, fontWeight: 600, fontFamily: "var(--mono)" }}>useEffect(fn, [])</text>
  <circle cx="230" cy="95" r="9" style={{ fill: "var(--ok)" }} />
  <circle cx="300" cy="95" r="9" style={{ fill: "var(--line)" }} />
  <circle cx="370" cy="95" r="9" style={{ fill: "var(--line)" }} />
  <circle cx="440" cy="95" r="9" style={{ fill: "var(--line)" }} />
  <text x="10" y="135" textAnchor="start" style={{ fill: "var(--ink2)", fontSize: 12, fontWeight: 600, fontFamily: "var(--mono)" }}>useEffect(fn, [likes])</text>
  <circle cx="230" cy="130" r="9" style={{ fill: "var(--ok)" }} />
  <circle cx="300" cy="130" r="9" style={{ fill: "var(--ok)" }} />
  <circle cx="370" cy="130" r="9" style={{ fill: "var(--line)" }} />
  <circle cx="440" cy="130" r="9" style={{ fill: "var(--ok)" }} />
</svg>
<figcaption>Green means the effect runs in that render. The array decides when.</figcaption>
</figure>

<div className="tscroll">

| Form | When the effect runs | Use it for |
|---|---|---|
| No array: `useEffect(fn)` | After **every** render | Rarely needed |
| Empty: `useEffect(fn, [])` | Once, after mounting | Loading data once, starting a timer |
| With values: `useEffect(fn, [a, b])` | After mounting, and when `a` or `b` changes | Reacting to a change |

</div>

      <button onClick={() => setLikes(likes + 1)}>Like ({likes})</button>{" "}
      <button onClick={() => setViews(views + 1)}>View ({views})</button>
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

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

On load, all three effects run. Click **View**: only "No array" runs. Click **Like**: "No array" and "[likes]" run. The "[]" effect never runs again.

## Cleanup Functions

A **cleanup function** is the function you **return** from an effect. React runs it at two times:

- **Before the effect runs again**, because a dependency changed. This cleans up the old run.
- **When the component unmounts.**

Without cleanup, timers keep running and listeners keep listening after the component is gone. This wastes memory and can cause errors.

<figure className="react-fig">
<svg viewBox="0 0 520 120" role="img" aria-labelledby="fig-l-cleanup" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-l-cleanup">Cleanup runs before the effect runs again, and when the component is removed</title>
  <rect x="5" y="35" width="95" height="44" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="52.5" y="54.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Effect</text>
  <text x="52.5" y="71.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>start timer</text>
  <path d="M100 57 L128 57" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M128 57 L119.7 60.5 L119.7 53.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="132" y="35" width="95" height="44" rx="8" style={{ fill: "var(--bad-soft)", stroke: "var(--bad)", strokeWidth: 1.5 }} />
  <text x="179.5" y="54.0" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Cleanup</text>
  <text x="179.5" y="71.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>stop timer</text>
  <path d="M227 57 L255 57" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M255 57 L246.7 60.5 L246.7 53.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="259" y="35" width="95" height="44" rx="8" style={{ fill: "var(--accent-secondary-soft)", stroke: "var(--accent-secondary)", strokeWidth: 1.5 }} />
  <text x="306.5" y="54.0" textAnchor="middle" style={{ fill: "var(--accent-secondary)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Effect</text>
  <text x="306.5" y="71.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>start again</text>
  <path d="M354 57 L382 57" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M382 57 L373.7 60.5 L373.7 53.5 Z" style={{ fill: "var(--faint)" }} />
  <rect x="386" y="35" width="129" height="44" rx="8" style={{ fill: "var(--bad-soft)", stroke: "var(--bad)", strokeWidth: 1.5 }} />
  <text x="450.5" y="54.0" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>Cleanup</text>
  <text x="450.5" y="71.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>on unmount</text>
  <text x="180" y="108" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>a dependency changed</text>
  <text x="450" y="108" textAnchor="middle" style={{ fill: "var(--muted)", fontSize: 11, fontWeight: 400, fontFamily: "var(--sans)" }}>component removed</text>
  <circle r="6" style={{ fill: "var(--accent-secondary)" }}>
    <animateMotion dur="3s" repeatCount="indefinite" path="M100 57 L128 57 M227 57 L255 57 M354 57 L382 57" />
  </circle>
</svg>
<figcaption>Every effect that starts something should stop it in its cleanup.</figcaption>
</figure>

      <button onClick={() => setReading(!reading)}>{reading ? "Stop reading" : "Start reading"}</button>
      {reading && <Timer />}
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

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

The seconds go up. Click **Stop reading**. The console says "Timer stopped", because the cleanup ran `clearInterval(id)`. Click **Start reading**, and a new timer starts from 0.

Look at these lines:

```tsx
const id = setInterval(...);    // start: keep the timer's id
return () => clearInterval(id); // cleanup: stop that timer
```

The effect keeps the timer's `id`, so the cleanup knows which timer to stop.

## Preventing Infinite Effect Loops

An **infinite loop** happens when an effect changes a value that is also in its own dependency array. The effect runs, changes the value, so it runs again, changes it again, and so on forever. The page freezes or React stops it with an error.

<figure className="react-fig">
<svg viewBox="0 0 520 150" role="img" aria-labelledby="fig-l-loop" style={{ width: "100%", maxWidth: 520, height: "auto" }}>
  <title id="fig-l-loop">Infinite loop: the effect sets count, count changes, the effect runs again, forever</title>
  <rect x="40" y="20" width="170" height="44" rx="8" style={{ fill: "var(--bad-soft)", stroke: "var(--bad)", strokeWidth: 1.5 }} />
  <text x="125.0" y="39.0" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>Effect runs</text>
  <text x="125.0" y="56.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>setCount(count + 1)</text>
  <rect x="310" y="20" width="170" height="44" rx="8" style={{ fill: "var(--bad-soft)", stroke: "var(--bad)", strokeWidth: 1.5 }} />
  <text x="395.0" y="39.0" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 13, fontWeight: 700, fontFamily: "var(--sans)" }}>count changes</text>
  <text x="395.0" y="56.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>React re-renders</text>
  <rect x="175" y="96" width="170" height="44" rx="8" style={{ fill: "var(--bad-soft)", stroke: "var(--bad)", strokeWidth: 1.5 }} />
  <text x="260.0" y="115.0" textAnchor="middle" style={{ fill: "var(--bad)", fontSize: 13, fontWeight: 700, fontFamily: "var(--mono)" }}>[count] changed</text>
  <text x="260.0" y="132.0" textAnchor="middle" style={{ fill: "var(--ink2)", fontSize: 11, fontFamily: "var(--sans)" }}>run the effect again</text>
  <path d="M210 42 L306 42" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M306 42 L297.7 45.5 L297.7 38.5 Z" style={{ fill: "var(--faint)" }} />
  <path d="M395 64 L330 96" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M330 96 L335.9 89.2 L339.0 95.5 Z" style={{ fill: "var(--faint)" }} />
  <path d="M190 96 L125 64" style={{ stroke: "var(--faint)", strokeWidth: 2, fill: "none" }} />
  <path d="M125 64 L134.0 64.5 L130.9 70.8 Z" style={{ fill: "var(--faint)" }} />
  <circle r="6" style={{ fill: "var(--bad)" }}>
    <animateMotion dur="2s" repeatCount="indefinite" path="M210 42 L306 42 L395 64 L330 96 L190 96 L125 64 L210 42" />
  </circle>
</svg>
<figcaption>This circle never stops. Add a stop condition, or do not use an effect.</figcaption>
</figure>

```tsx
// Wrong: count changes, so the effect runs, so count changes...
useEffect(() => {
  setCount(count + 1);
}, [count]);
```

Another common cause is an effect with **no array** that sets state. Every render runs the effect, the effect sets state, and setting state causes a render.

```tsx
// Wrong: no array, and the effect sets state
useEffect(() => {
  setBooks(loadBooks());
});
```

How to avoid loops:

- **Always write a dependency array.** Use `[]` for "once".
- **Do not set a state that the effect depends on**, unless there is a condition that stops it.
- **Ask: do I need an effect at all?** If you can work out a value from props or state, just calculate it in the component. No effect and no extra state needed.

```tsx
// No effect needed: just calculate it
const total = books.length * 299;
```

      <p>{stock > 0 ? "Only " + stock + " copies left!" : "Sold out."}</p>
      <button onClick={() => setStock(3)}>Restock</button>
    </div>
  );
}
`,
    "main.tsx": `import { createRoot } from "react-dom/client";

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

This effect depends on `stock` and changes `stock`. It does not loop forever, because of `if (stock > 0)`. When the stock reaches 0, the effect stops changing it.

Before you write an effect, ask: "Is this work outside React?" If not, you probably do not need an effect.

## Common mistakes

<div className="tscroll">

| Mistake | What you see | Fix |
|---|---|---|
| Effect with no dependency array that sets state | The page freezes: an infinite loop | Add a dependency array |
| Setting a state that is in the effect's own array | An infinite loop | Add a stop condition, or remove the effect |
| Leaving out a value the effect uses | The effect uses an old value | List every value the effect uses |
| Forgetting to clear a timer or listener | It keeps running after the component is gone | Return a cleanup function |
| Doing side work (like timers) directly in the component body | It runs on every render | Move it into `useEffect` |
| Using an effect to calculate a value from state | Extra renders and extra state | Calculate it directly in the component |
| Expecting `[]` effects to see new state | The effect shows the starting value | Add the state to the array |

</div>

## Check Yourself

## Exercises

## What to Remember

- A component mounts once, updates many times, and unmounts once. Effects run after the screen updates.
- The dependency array decides when an effect runs: none = every render, `[]` = once, `[a]` = when `a` changes.
- Return a cleanup function to stop timers and listeners, and add a stop condition so an effect never loops forever.

Next: Application State Management
