> ## Documentation Index
> Fetch the complete documentation index at: https://js.maxbraglia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Side effects with useEffect

> Fetch data, set up subscriptions, and run code when your component mounts

## What are side effects?

React components have one job: return JSX based on props and state. Anything else — fetching data, updating the document title, setting up a timer — is a **side effect**. It's something that happens *outside* the render cycle.

```jsx theme={null}
function UserList() {
  const [users, setUsers] = useState([]);

  // This is a side effect — fetching data from an API
  // Where does it go? useEffect.
}
```

`useEffect` is the hook for running side effects. In this course, you'll use it mostly for **fetching data** and **syncing with browser APIs** (like document title or timers).

<Info>
  Python mental model: your component function is like a function React may call many times to calculate UI. Keep that part "pure" (return JSX from props/state). `useEffect` is where you register code that should run **after render** to sync with the outside world (network, browser APIs, timers).
</Info>

## Basic useEffect

```jsx theme={null}
import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    // This runs after the component renders
    console.log("Component mounted!");
  }, []); // Empty array = run once on mount

  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
```

`useEffect` takes two arguments:

1. A **function** — the code to run (the effect)
2. A **dependency array** — when to re-run it

## The dependency array

The dependency array controls when the effect runs:

<Tip>
  Python mental model: the dependency array is the list of values React watches. You're telling React, "re-run this sync code when these inputs change."
</Tip>

```jsx theme={null}
// Runs once — when the component mounts
useEffect(() => {
  fetchUsers();
}, []);

// Runs when userId changes
useEffect(() => {
  fetchUser(userId);
}, [userId]);

// Runs when either userId or role changes
useEffect(() => {
  fetchUserData(userId, role);
}, [userId, role]);

// ⚠️ Runs on EVERY render (usually a mistake)
useEffect(() => {
  console.log("Rendered!");
}); // No array at all
```

| Dependency array | When it runs                           |
| ---------------- | -------------------------------------- |
| `[]`             | Once, when component mounts            |
| `[userId]`       | On mount + whenever `userId` changes   |
| `[a, b]`         | On mount + whenever `a` or `b` changes |
| *(omitted)*      | Every single render (rarely wanted)    |

<Info>
  In development, `React.StrictMode` may run mount effects twice to help reveal bugs. For a simple `useEffect(..., [])` data-fetch example, that can mean two requests in development unless you guard against it. Production does not do the extra development check.
</Info>

<Warning>
  `[]` is a common beginner pattern for "run on mount" effects (like an initial fetch), but it's not the default for every effect. As a rule: put every prop/state value your effect uses into the dependency array. Omitting the array entirely causes the effect to run on every render, which is usually a bug.
</Warning>

## Fetching data on mount

This is the pattern you'll use most often — load data when a component first appears:

```jsx theme={null}
import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function loadUsers() {
      try {
        const response = await fetch("http://localhost:8000/api/users");
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    loadUsers();
  }, []); // Empty array = fetch once on mount

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name} — {user.email}</li>
      ))}
    </ul>
  );
}
```

<Info>
  Notice the async function is defined *inside* the useEffect callback, not as the callback itself. This is because useEffect callbacks can't be async directly — they need to return either nothing or a cleanup function.
</Info>

### Fetching based on a prop or state value

When the data depends on a value (like a user ID from the URL), include it in the dependency array:

```jsx theme={null}
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadUser() {
      setLoading(true);
      try {
        const response = await fetch(`/api/users/${userId}`);
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        setUser(await response.json());
      } catch (err) {
        setUser(null);
      } finally {
        setLoading(false);
      }
    }

    loadUser();
  }, [userId]); // Re-fetch when userId changes

  if (loading) return <p>Loading...</p>;
  if (!user) return <p>User not found</p>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}
```

When `userId` changes (e.g., navigating from one user profile to another), the effect runs again and fetches the new user.

## Other common side effects

### Updating the document title

```jsx theme={null}
function ProductPage({ product }) {
  useEffect(() => {
    document.title = `${product.name} — My Store`;
  }, [product.name]);

  return <h1>{product.name}</h1>;
}
```

### Setting up a timer

```jsx theme={null}
function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    // Cleanup: clear the interval when component unmounts
    return () => clearInterval(interval);
  }, []);

  return <p>Elapsed: {seconds}s</p>;
}
```

## Cleanup function

The function you return from useEffect runs when the component **unmounts** (disappears) or before the effect re-runs:

```jsx theme={null}
useEffect(() => {
  // Setup: runs when component mounts (or dependencies change)
  const subscription = connectToWebSocket(userId);

  // Cleanup: runs when component unmounts (or before next run)
  return () => {
    subscription.close();
  };
}, [userId]);
```

Cleanup prevents memory leaks and stale data. Common cleanup tasks:

* Clearing timers (`clearInterval`, `clearTimeout`)
* Closing WebSocket connections
* Cancelling pending requests
* Removing event listeners

<Tip>
  You won't need cleanup for most simple fetch-on-mount examples. Cleanup is mainly for subscriptions, timers, and event listeners. In development, StrictMode may re-run effects and expose problems earlier, but don't rely on it to catch every cleanup bug.
</Tip>

## Common mistakes

<AccordionGroup>
  <Accordion title="Missing dependency array — infinite loop">
    ```jsx theme={null}
    // ❌ Infinite loop: fetches on every render
    useEffect(() => {
      fetch("/api/users")
        .then(r => r.json())
        .then(data => setUsers(data)); // setUsers triggers re-render → effect runs again → ...
    });

    // ✅ Add empty dependency array
    useEffect(() => {
      fetch("/api/users")
        .then(r => r.json())
        .then(data => setUsers(data));
    }, []); // Only runs once
    ```

    <Warning>
      Forgetting the dependency array creates an infinite loop: render → effect → state update → re-render → effect → ... Your browser tab will freeze and your API will get hammered.
    </Warning>
  </Accordion>

  <Accordion title="Making useEffect callback async directly">
    ```jsx theme={null}
    // ❌ Wrong: useEffect callback can't be async
    useEffect(async () => {
      const response = await fetch("/api/users");
      const data = await response.json();
      setUsers(data);
    }, []);

    // ✅ Correct: define async function inside, then call it
    useEffect(() => {
      async function loadUsers() {
        const response = await fetch("/api/users");
        const data = await response.json();
        setUsers(data);
      }

      loadUsers();
    }, []);
    ```

    <Warning>
      `useEffect` callbacks must return either nothing or a cleanup function. An async function returns a Promise, which React doesn't know how to handle. Define the async function inside the effect and call it immediately.
    </Warning>
  </Accordion>

  <Accordion title="Object or array as dependency (re-runs every render)">
    ```jsx theme={null}
    // ❌ Runs on every render because {} !== {}
    const options = { page: 1, limit: 10 };
    useEffect(() => {
      fetchUsers(options);
    }, [options]); // New object every render!

    // ✅ Use primitive values as dependencies
    useEffect(() => {
      fetchUsers({ page, limit });
    }, [page, limit]); // Primitives are compared by value
    ```

    <Warning>
      Objects and arrays are compared by **reference**, not by value. `{a: 1} !== {a: 1}`. Use primitive values (strings, numbers, booleans) in the dependency array whenever possible.
    </Warning>
  </Accordion>
</AccordionGroup>

## The useEffect mental model

Think of useEffect in terms of **synchronization**, not lifecycle:

```
"Keep this side effect synchronized with these values"
```

* `useEffect(fn, [])` — synchronize with nothing (run once)
* `useEffect(fn, [userId])` — synchronize with userId (re-run when it changes)
* `useEffect(fn, [a, b])` — synchronize with a and b

<Tip>
  If you're coming from Python or other frameworks, resist thinking in "component lifecycle" terms (mount, update, unmount). Think instead: "this effect should run when these values change." The dependency array is the key to understanding useEffect.
</Tip>

<Info>
  Also remember: `useEffect` callbacks are closures. They capture values from the render where they were created. That's why missing a dependency can make an effect use an old value ("stale closure").
</Info>

## What's next?

You can fetch data and manage side effects. Now let's learn how to conditionally show different UI based on that data — loading spinners, error messages, and content.

<Card title="Conditional rendering" icon="code-branch" href="/react-essentials/conditional-rendering">
  Show different UI based on state and conditions
</Card>
