React Lifecycle Methods and useEffect

React lifecycle methods are special methods that get called at specific points during the life of a component. They provide hooks for various stages such as mounting, updating, and unmounting.

In class components, you handle lifecycle events using lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount. However, with the introduction of hooks, functional components can handle lifecycle events using the useEffect hook.

In this module, we will cover:

  • React lifecycle methods in class components
  • The useEffect hook and how it replaces lifecycle methods in functional components
  • How to use useEffect for fetching data, subscribing to events, and cleaning up resources
  • Best practices for using useEffect effectively

React Lifecycle Methods in Class Components

Before hooks were introduced, class components used lifecycle methods to perform actions at various points in the component’s life. These methods are called automatically at specific times during a component’s existence.

Key Lifecycle Methods in Class Components:

  1. componentDidMount: This method is called once, immediately after the component is mounted (inserted into the tree).
  2. componentDidUpdate: This method is called after the component updates, i.e., when the state or props change.
  3. componentWillUnmount: This method is called just before the component is unmounted and destroyed. It is typically used to clean up resources like timers or event listeners.

Example of Lifecycle Methods in a Class Component:

jsxCopyEditimport React, { Component } from 'react';

class UserList extends Component {
  constructor(props) {
    super(props);
    this.state = { users: [] };
  }

  // Fetch data when the component mounts
  componentDidMount() {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(response => response.json())
      .then(data => this.setState({ users: data }));
  }

  // Clean up before unmounting
  componentWillUnmount() {
    console.log('UserList component is being removed');
  }

  render() {
    return (
      <div>
        <h1>Users</h1>
        <ul>
          {this.state.users.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      </div>
    );
  }
}

export default UserList;

In this example:

  • componentDidMount fetches the list of users when the component is first rendered.
  • componentWillUnmount could be used for cleanup actions (e.g., removing event listeners or cancelling API calls).

The useEffect Hook in Functional Components

The useEffect hook is used in functional components to perform side effects, such as fetching data, setting up subscriptions, or cleaning up resources. The useEffect hook can replace most lifecycle methods from class components.

Basic Syntax of useEffect:

jsxCopyEdituseEffect(() => {
  // Code to run when the component mounts or updates

  return () => {
    // Cleanup code, runs when the component unmounts or before the effect is re-run
  };
}, [dependencies]);
  • The effect callback (first argument) runs after every render by default.
  • The cleanup function (optional return from the effect) runs before the component unmounts or before the effect is re-run.
  • The dependency array (second argument) allows you to control when the effect runs, by specifying which state or props to watch.

Using useEffect for Side Effects

Example 1: Fetching Data with useEffect

In class components, we use componentDidMount to fetch data. In functional components, we can achieve the same using useEffect.

jsxCopyEditimport React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(response => response.json())
      .then(data => setUsers(data));
  }, []); // Empty dependency array means this effect runs only once (like componentDidMount)

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

export default UserList;
  • The effect runs once when the component mounts because the dependency array is empty ([]).
  • If you want the effect to run when a specific state or prop changes, you can include that state or prop in the dependency array.

Example 2: Subscribing to an Event with useEffect

You can also use useEffect to set up event listeners or subscriptions. For example, you can listen for window resize events and update the state accordingly.

jsxCopyEditimport React, { useState, useEffect } from 'react';

const WindowSize = () => {
  const [windowWidth, setWindowWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => {
      setWindowWidth(window.innerWidth);
    };

    window.addEventListener('resize', handleResize);

    // Cleanup the event listener when the component unmounts
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Empty dependency array means this effect runs only once when the component mounts

  return (
    <div>
      <h1>Window Width: {windowWidth}</h1>
    </div>
  );
};

export default WindowSize;
  • The event listener is added when the component mounts, and it is cleaned up when the component unmounts by returning a cleanup function from the useEffect hook.

Using useEffect with Dependencies

The useEffect hook allows you to specify dependencies that determine when the effect should run. If a state or prop inside the dependency array changes, React will re-run the effect.

Example: Updating Based on State Changes

jsxCopyEditimport React, { useState, useEffect } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // Effect that runs whenever `count` changes
    document.title = `Count: ${count}`;
  }, [count]); // The effect runs only when `count` changes

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
};

export default Counter;
  • The effect updates the document.title to reflect the current value of count.
  • The effect is triggered only when the count value changes, thanks to the dependency array [count].

Cleanup with useEffect

If an effect involves resources that need to be cleaned up (such as event listeners, timers, or subscriptions), you can return a cleanup function inside useEffect. This function will run before the effect re-runs or when the component unmounts.

Example of Cleanup (Timer):

jsxCopyEditimport React, { useState, useEffect } from 'react';

const Timer = () => {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const intervalId = setInterval(() => {
      setSeconds(prevSeconds => prevSeconds + 1);
    }, 1000);

    // Cleanup function to clear the interval when the component unmounts
    return () => clearInterval(intervalId);
  }, []); // Empty dependency array means this effect runs only once when the component mounts

  return (
    <div>
      <h1>{seconds} seconds</h1>
    </div>
  );
};

export default Timer;
  • The timer is started when the component mounts and cleaned up when the component unmounts, preventing any memory leaks.

Best Practices for Using useEffect

  1. Specify Dependencies: Always include dependencies in the dependency array to control when the effect should run. If an effect doesn’t have any dependencies, use an empty array [] to make it run only once on mount and unmount.
  2. Cleanup: Use the cleanup function to clean up resources such as timers, event listeners, and subscriptions.
  3. Multiple Effects: You can use multiple useEffect hooks in the same component to handle different side effects independently.
  4. Avoid Complex Logic Inside useEffect: Keep the logic inside useEffect simple and clean. Complex logic should be moved outside of useEffect for readability and maintainability.

Summary

In this module, we covered:

  • React lifecycle methods in class components (componentDidMount, componentDidUpdate, componentWillUnmount)
  • How to handle lifecycle events using the useEffect hook in functional components
  • Performing side effects such as fetching data, subscribing to events, and cleaning up resources with useEffect
  • Best practices for using useEffect effectively and avoiding common pitfalls

The useEffect hook is essential for handling side effects in React functional components, and mastering it will make your applications more powerful and efficient.