React Lifecycle and useEffect Hook

Introduction

So far, you’ve learned how to build components, manage state, handle events, render lists, and work with forms. Now, it’s time to understand what happens behind the scenes when components load, update, and are removed.

In React, every component goes through a lifecycle — a series of stages from creation to removal. In functional components, we manage these lifecycle behaviors using the useEffect hook.

What Is a Component Lifecycle?

A component lifecycle refers to the different phases a component goes through:

  • Mounting – when the component is created and added to the screen

  • Updating – when state or props change

  • Unmounting – when the component is removed from the screen

In older class components, special lifecycle methods handled these phases. In modern React, the useEffect hook replaces most of that functionality.

What is the useEffect Hook?

The useEffect hook lets you perform side effects in functional components. Side effects are operations that interact with things outside the component, such as:

  • Fetching data from an API

  • Setting timers

  • Updating the document title

  • Subscribing to events

Basic syntax:

import { useEffect } from "react";

useEffect(() => {
  // Side effect code here
});

Running useEffect on Every Render

If you don’t provide a dependency array, useEffect runs after every render.

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

This runs after the component first loads and after every update.

Running useEffect Only Once (On Mount)

To run an effect only when the component first loads, pass an empty dependency array.

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

This behaves like the mounting phase.

Running useEffect When Data Changes

You can control when the effect runs by adding dependencies.

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

Now, the effect runs only when count changes.

Cleaning Up Effects (Unmounting)

Some effects need cleanup, such as timers or subscriptions. useEffect can return a cleanup function.

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

  return () => {
    clearInterval(timer);
    console.log("Component unmounted");
  };
}, []);

The returned function runs when the component unmounts.

Example: Fetching Data

import { useState, useEffect } from "react";

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

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

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

The data loads once when the component mounts.

Common Mistakes to Avoid

  • Forgetting the dependency array

  • Causing infinite loops by updating the state inside useEffect without dependencies

  • Not cleaning up timers or subscriptions

Summary

In this chapter, you learned how React components go through lifecycle phases and how the useEffect hook manages side effects. You saw how to run effects on mount, on updates, and how to clean up resources. This knowledge helps you handle real-world tasks such as fetching data and managing timers efficiently.