# React useEffect – Side Effects & Component Lifecycle

After learning **Lifting State Up** on Day 68, we now understand how components can share and coordinate state.

But there is another important question:

> **What happens when our component needs to do something outside of rendering UI?**

For example:

*   Fetch data from an API
    
*   Start a timer
    
*   Listen for browser events
    
*   Update the document title
    
*   Subscribe to something
    
*   Read or interact with browser APIs
    
*   Perform an action when state changes
    

These are called **side effects**, and React provides the `useEffect` Hook to handle them.

* * *

## 🔹 What Is a Side Effect?

A side effect is an operation that happens **outside the normal process of calculating and rendering JSX**.

For example:

```jsx
function App() {
  document.title = "My React App";

  return <h1>Hello React</h1>;
}
```

This changes something outside the React component itself.

That's a side effect.

Other examples:

```text
API requests
Timers
Console logging
DOM manipulation
Event listeners
Subscriptions
Browser storage
WebSocket connections
```

React rendering should ideally remain **pure**.

That means:

```text
Props + State
     ↓
   Render
     ↓
    JSX
```

Side effects happen separately:

```text
Render
  ↓
React commits UI
  ↓
useEffect runs
  ↓
External operation
```

* * *

# 🔹 What Is `useEffect`?

`useEffect` is a React Hook used to synchronize a component with an **external system**.

Basic syntax:

```jsx
import { useEffect } from "react";

useEffect(() => {
  // Side effect
});
```

For example:

```jsx
import { useEffect } from "react";

function App() {
  useEffect(() => {
    console.log("Effect executed");
  });

  return <h1>Hello React</h1>;
}
```

The effect runs after React renders the component.

* * *

# 🔹 The Dependency Array

The dependency array determines **when an effect should run**.

There are three important patterns.

* * *

## 1️⃣ No Dependency Array

```jsx
useEffect(() => {
  console.log("Effect executed");
});
```

This runs after **every render**.

Example:

```jsx
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("Component rendered");
  });

  return (
    <>
      <h1>{count}</h1>

      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </>
  );
}
```

Every state update causes another render.

Therefore:

```text
Initial render → Effect
count update   → Render → Effect
count update   → Render → Effect
```

* * *

# 2️⃣ Empty Dependency Array

```jsx
useEffect(() => {
  console.log("Effect executed");
}, []);
```

The empty array means:

> Run this effect when the component is mounted.

Example:

```jsx
function App() {
  useEffect(() => {
    console.log("Component mounted");
  }, []);

  return <h1>Hello</h1>;
}
```

This is commonly used for things such as:

```text
Initial API request
Initial setup
Browser event listeners
Initial subscriptions
```

* * *

# 3️⃣ Dependencies

You can provide values that the effect depends on.

```jsx
useEffect(() => {
  console.log("Count changed");
}, [count]);
```

Now the effect runs when `count` changes.

Example:

```jsx
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("Count:", count);
  }, [count]);

  return (
    <>
      <h1>{count}</h1>

      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </>
  );
}
```

The dependency array tells React:

> "Run this effect again whenever one of these values changes."

* * *

# 🔹 Understanding the Dependency Array

Think of it like this:

| Dependency | Behavior |
| --- | --- |
| No array | After every render |
| `[]` | After initial mount |
| `[count]` | When `count` changes |
| `[count, name]` | When either changes |

Example:

```jsx
useEffect(() => {
  console.log("Effect");
}, [count, name]);
```

The effect depends on both `count` and `name`.

* * *

# 🔹 `useEffect` and State

One of the most common patterns is reacting to state changes.

```jsx
const [name, setName] = useState("");

useEffect(() => {
  console.log(`Hello ${name}`);
}, [name]);
```

Whenever `name` changes:

```text
name changes
     ↓
React re-renders
     ↓
Effect checks dependencies
     ↓
name changed
     ↓
Effect runs
```

* * *

# 🔹 Updating the Browser Tab Title

A simple and useful example:

```jsx
function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);

  return (
    <>
      <h1>{count}</h1>

      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </>
  );
}
```

Now the browser tab changes as the counter changes.

This is a perfect example of a side effect because:

```text
React State
    ↓
useEffect
    ↓
Browser document title
```

* * *

# 🔹 Fetching Data With `useEffect`

One of the most common real-world use cases is fetching data.

Example:

```jsx
import { useEffect, useState } from "react";

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

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((response) => response.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);

  return (
    <div>
      {users.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  );
}
```

The flow is:

```text
Component mounts
       ↓
useEffect runs
       ↓
API request
       ↓
Data received
       ↓
setUsers()
       ↓
Component re-renders
       ↓
Users displayed
```

This pattern is extremely important for frontend development.

* * *

# 🔹 Loading State

Real applications need to show users what's happening.

```jsx
function Users() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((response) => response.json())
      .then((data) => {
        setUsers(data);
        setLoading(false);
      });
  }, []);

  if (loading) {
    return <h2>Loading...</h2>;
  }

  return (
    <div>
      {users.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  );
}
```

Now the UI has two states:

```text
Loading
   ↓
API response
   ↓
Users displayed
```

* * *

# 🔹 Error Handling

A production application also needs an error state.

```jsx
function Users() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((response) => {
        if (!response.ok) {
          throw new Error("Failed to fetch users");
        }

        return response.json();
      })
      .then((data) => {
        setUsers(data);
      })
      .catch((error) => {
        setError(error.message);
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);

  if (loading) {
    return <h2>Loading...</h2>;
  }

  if (error) {
    return <h2>{error}</h2>;
  }

  return (
    <div>
      {users.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  );
}
```

Now we have:

```text
Loading
   ↓
Success → Display data

OR

Error → Display error message
```

This is a common pattern in real React applications.

* * *

# 🔹 Cleanup Functions

Some effects create resources that need to be cleaned up.

Examples:

```text
Timers
Event listeners
Subscriptions
WebSocket connections
```

`useEffect` allows us to return a cleanup function.

```jsx
useEffect(() => {
  // Setup

  return () => {
    // Cleanup
  };
}, []);
```

* * *

# 🔹 Timer Example

```jsx
useEffect(() => {
  const timer = setInterval(() => {
    console.log("Running...");
  }, 1000);

  return () => {
    clearInterval(timer);
  };
}, []);
```

The effect creates a timer.

The cleanup removes it.

```text
Component mounted
      ↓
Timer starts
      ↓
Component unmounts
      ↓
Cleanup runs
      ↓
Timer stops
```

Without cleanup, resources can continue running unnecessarily.

* * *

# 🔹 Event Listener Example

Suppose we want to listen for window resize events.

```jsx
useEffect(() => {
  function handleResize() {
    console.log(window.innerWidth);
  }

  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);
```

Notice that we remove the **same function** we added.

This is important.

* * *

# 🔹 Component Lifecycle

Although modern React emphasizes effects as synchronization with external systems rather than lifecycle methods, it's useful to understand the traditional lifecycle idea.

A component goes through stages:

```text
Mount
  ↓
Update
  ↓
Unmount
```

### Mount

Component appears on the screen.

### Update

State or props change.

### Unmount

Component is removed from the UI.

`useEffect` can participate in these transitions through its setup and cleanup behavior.

* * *

# 🔹 Cleanup Before Re-running an Effect

This is an important concept.

Suppose:

```jsx
useEffect(() => {
  console.log("Effect");

  return () => {
    console.log("Cleanup");
  };
}, [count]);
```

When `count` changes, React conceptually does:

```text
Previous effect
      ↓
Cleanup
      ↓
New effect
```

This becomes especially important for:

```text
Subscriptions
Event listeners
Timers
Connections
```

* * *

# 🔹 `useEffect` With Props

Effects can depend on props as well.

```jsx
function UserProfile({ userId }) {
  useEffect(() => {
    console.log("Fetching user:", userId);
  }, [userId]);

  return <h1>User Profile</h1>;
}
```

Whenever `userId` changes, the effect runs again.

This is useful when navigating between users:

```text
/user/1
   ↓
Fetch user 1

/user/2
   ↓
Fetch user 2
```

* * *

# 🔹 Don't Use `useEffect` for Everything

This is one of the most important lessons.

You don't need an effect just because something changes.

For example, this is unnecessary:

```jsx
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [fullName, setFullName] = useState("");

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
```

Instead:

```jsx
const fullName = `${firstName} ${lastName}`;
```

Why?

Because `fullName` is **derived data**.

It can be calculated directly during rendering.

* * *

# 🔹 Derived State vs Side Effect

This distinction is extremely important.

### Derived value

```jsx
const fullName = `${firstName} ${lastName}`;
```

No effect required.

### External synchronization

```jsx
useEffect(() => {
  document.title = fullName;
}, [fullName]);
```

Effect makes sense because we're interacting with something outside React.

Think:

> **If you can calculate it during render, don't automatically reach for** `useEffect`**.**

* * *

# 🔹 Common Mistake: Infinite Loops

Consider:

```jsx
useEffect(() => {
  setCount(count + 1);
}, [count]);
```

What's happening?

```text
count changes
   ↓
effect runs
   ↓
setCount()
   ↓
count changes
   ↓
effect runs
   ↓
setCount()
   ↓
...
```

This creates an infinite update loop.

Always ask:

> "Does this effect update a dependency that causes the effect to run again?"

* * *

# 🔹 Another Common Mistake: Missing Dependencies

Example:

```jsx
useEffect(() => {
  console.log(user);
}, []);
```

If the effect uses a reactive value such as `user`, but you intentionally leave it out of the dependency list, you can end up with stale values.

A better approach is generally:

```jsx
useEffect(() => {
  console.log(user);
}, [user]);
```

Modern React tooling can help detect dependency mistakes.

* * *

# 🔹 `useEffect` and `async`

A common mistake is:

```jsx
useEffect(async () => {
  // ❌ Don't do this
}, []);
```

Instead, define an async function inside the effect:

```jsx
useEffect(() => {
  async function fetchUsers() {
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/users"
    );

    const data = await response.json();

    setUsers(data);
  }

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

This keeps the effect callback's return value available for cleanup.

* * *

# 🔹 A Realistic Data Fetching Pattern

Here's a more complete example:

```jsx
import { useEffect, useState } from "react";

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

  useEffect(() => {
    async function fetchUsers() {
      try {
        setLoading(true);

        const response = await fetch(
          "https://jsonplaceholder.typicode.com/users"
        );

        if (!response.ok) {
          throw new Error("Unable to fetch users");
        }

        const data = await response.json();

        setUsers(data);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    }

    fetchUsers();
  }, []);

  if (loading) {
    return <h2>Loading users...</h2>;
  }

  if (error) {
    return <h2>Error: {error}</h2>;
  }

  return (
    <div>
      <h1>Users</h1>

      {users.map((user) => (
        <div key={user.id}>
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </div>
      ))}
    </div>
  );
}

export default Users;
```

This pattern combines several concepts you've already learned:

```text
useState
   +
useEffect
   +
API
   +
Conditional Rendering
   +
Lists
   +
Keys
```

That's exactly how React concepts begin coming together in real applications.

* * *

# 🔹 `useEffect` Mental Model

Instead of memorizing syntax, remember this:

```text
React renders UI
      ↓
Something outside React needs synchronization
      ↓
useEffect
      ↓
Perform external operation
      ↓
Cleanup when necessary
```

Examples:

```text
API request
Browser title
Timer
Event listener
Subscription
WebSocket
Storage interaction
```

* * *

# 🔹 Best Practices

### ✅ 1. Use effects for external synchronization

Don't use `useEffect` simply because you can.

* * *

### ✅ 2. Keep dependency arrays accurate

```jsx
useEffect(() => {
  console.log(name);
}, [name]);
```

* * *

### ✅ 3. Clean up resources

```jsx
return () => {
  clearInterval(timer);
};
```

* * *

### ✅ 4. Avoid unnecessary derived state

Instead of:

```jsx
useEffect(() => {
  setTotal(price * quantity);
}, [price, quantity]);
```

Prefer:

```jsx
const total = price * quantity;
```

* * *

### ✅ 5. Handle API states

Real applications should consider:

```text
Loading
Success
Error
Empty
```

* * *

### ✅ 6. Avoid infinite effect loops

Be careful when an effect updates one of its own dependencies.

* * *

### ✅ 7. Keep effects focused

One effect should ideally represent one synchronization concern.

* * *

# 🔹 `useEffect` Cheat Sheet

```jsx
// Every render
useEffect(() => {
  // ...
});
```

```jsx
// After initial mount
useEffect(() => {
  // ...
}, []);
```

```jsx
// When count changes
useEffect(() => {
  // ...
}, [count]);
```

```jsx
// With cleanup
useEffect(() => {
  const timer = setInterval(() => {
    console.log("Running");
  }, 1000);

  return () => {
    clearInterval(timer);
  };
}, []);
```

* * *

# My Biggest Takeaway

Today I learned that `useEffect` **isn't simply a "run some code after rendering" Hook.**

Its real purpose is to synchronize a React component with something **outside of React's rendering system**.

The most important mental model for me is:

> **Render describes the UI. Effects synchronize the UI with external systems.**

I also learned how dependency arrays control when effects run, why cleanup functions matter, how API requests fit into `useEffect`, and why unnecessary effects can make React applications harder to understand.

After learning state, events, forms, lists, conditional rendering, lifting state, and now effects, React is starting to feel much more like a complete application-building tool rather than just a UI library.
