React Component Lifecycle

Every React component has a "lifecycle" that you can think of as the various stages a component goes through from creation to destruction. Understanding the lifecycle allows you to execute code at different stages of a component, such as fetching data after the component's initial render, or performing cleanup before the component is removed.

A component's lifecycle is mainly divided into three phases:

  1. Mounting: The component instance is created and inserted into the DOM.
  2. Updating: The component's props or state change, causing a re-render.
  3. Unmounting: The component is removed from the DOM.

In function components, we mainly use the useEffect Hook to handle these lifecycle events.

Managing Lifecycle with useEffect Hook

The useEffect Hook is a powerful API that unifies the way to handle side effects when a component mounts, updates, and unmounts.

1. Mounting: componentDidMount

If you want to execute some code after the component is first rendered to the screen (for example, fetching data from a server), you can use useEffect with an empty dependency array [].

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

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // This function will execute after the component's first render
    console.log('Component has mounted');
    fetch(`https://api.example.com/users/${userId}`)
      .then(response => response.json())
      .then(data => setUser(data));

  }, []); // Empty array tells React this effect only needs to run once

  if (!user) {
    return <div>Loading...</div>;
  }

  return <div>{user.name}</div>;
}

2. Updating: componentDidUpdate

When you want to execute side effects after the component's props or state update, you can specify these props or state in the useEffect dependency array.

In the following example, whenever the userId prop changes, the component will fetch new user data.

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

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // This effect will run after the first render,
    // and will also run every time userId updates.
    console.log(`Fetching data for userId ${userId}`);
    fetch(`https://api.example.com/users/${userId}`)
      .then(response => response.json())
      .then(data => setUser(data));

  }, [userId]); // Dependency array includes userId

  // ...
}

If you don't provide a dependency array, the effect will run after every component render, which is usually not what we want because it can lead to unnecessary performance overhead.

3. Unmounting: componentWillUnmount

Some side effects need cleanup, such as timers or event listeners. useEffect allows you to return a cleanup function. This function will execute before the component is removed from the DOM, and will also execute before the next effect runs.

Let's look at an example of subscribing to a chat room status:

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

function ChatRoom({ roomId }) {
  const [status, setStatus] = useState('offline');

  useEffect(() => {
    console.log(`Subscribed to room ${roomId}`);
    ChatAPI.subscribeToRoomStatus(roomId, (newStatus) => {
      setStatus(newStatus);
    });

    // Return a cleanup function
    return () => {
      console.log(`Unsubscribed from room ${roomId}`);
      ChatAPI.unsubscribeFromRoomStatus(roomId);
    };
  }, [roomId]); // When roomId changes, the old subscription will be cancelled first, then a new one set up

  return <div>Room status: {status}</div>;
}

Summary

useEffect provides a unified, powerful way for function components to handle various side effects in the lifecycle:

Lifecycle EventuseEffect Usage
MountinguseEffect(() => { ... }, [])
UpdatinguseEffect(() => { ... }, [dep1, dep2])
UnmountinguseEffect(() => { return () => { ... } }, [])

With useEffect, we can organize related logic (such as subscribing and unsubscribing) together, making the code clearer and easier to maintain. Next, we'll dive into React component APIs.