No history yet

Mapping Class Lifecycles

From Lifecycles to Synchronisation

Moving from class components to functional components in React involves a crucial mental shift. Instead of thinking about distinct lifecycle phases like mounting, updating, and unmounting, we start thinking about synchronising our component's state with the world outside of React. This 'outside world' could be a network request, the browser's DOM, or a timer. The main tool for this job is the useEffect hook.

The core idea is this: your effects synchronise your component with an external system, and they keep doing so over time.

In class components, logic for a single feature was often split across multiple lifecycle methods. For example, you might fetch data in componentDidMount and set up a subscription, then tear down that subscription in componentWillUnmount. Hooks allow you to group related logic together.

Mounting and Updating

Let's map the old lifecycle methods to their useEffect equivalents. The key is the —the second argument to useEffect. This array tells React when to re-run your effect.

To mimic componentDidMount, you provide an empty dependency array []. This tells React the effect doesn't depend on any props or state, so it only needs to run once after the initial render.

// Class component
componentDidMount() {
  document.title = 'Component Mounted';
}

// Functional equivalent
import { useEffect } from 'react';

useEffect(() => {
  document.title = 'Component Mounted';
}, []); // Empty array means run once

To mimic componentDidUpdate, you can omit the dependency array entirely. This causes the effect to run after every single render. This is often inefficient and can cause infinite loops if you're not careful.

// Runs after every render
useEffect(() => {
  console.log('Component re-rendered');
});

A much more common and useful pattern is to provide specific values in the dependency array. The effect will then only re-run if one of those values changes between renders. This is perfect for reacting to changes in specific props or state.

// Class component
componentDidUpdate(prevProps) {
  if (prevProps.userId !== this.props.userId) {
    this.fetchData(this.props.userId);
  }
}

// Functional equivalent
useEffect(() => {
  fetchData(props.userId);
}, [props.userId]); // Re-runs only if userId changes

Cleaning Up Effects

What about componentWillUnmount? This is where you would clean up timers, event listeners, or network subscriptions to prevent in your application.

In useEffect, you achieve this by returning a function from your effect. React will execute this cleanup function when the component unmounts.

// Class component
class ChatAPI extends React.Component {
  componentDidMount() {
    API.subscribeToFriendStatus(this.props.friend.id, this.handleStatusChange);
  }

  componentWillUnmount() {
    API.unsubscribeFromFriendStatus(this.props.friend.id, this.handleStatusChange);
  }

  handleStatusChange = (status) => {
    // ...
  }
}

With useEffect, the setup and cleanup logic live together, making the code easier to read and maintain. The logic is co-located.

// Functional equivalent
function FriendStatus(props) {
  useEffect(() => {
    // This is the setup part (like componentDidMount)
    API.subscribeToFriendStatus(props.friend.id, handleStatusChange);

    // Return a function to handle cleanup (like componentWillUnmount)
    return () => {
      API.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  }, [props.friend.id]); // Don't forget dependencies!

  // ...
}

Crucially, the cleanup function also runs before the effect runs again for a subsequent render (but not the first one). This ensures that you don't have multiple subscriptions active at the same time if a prop like props.friend.id changes.

Time for a quick check-in. Test your understanding of how useEffect maps to class lifecycles.

Quiz Questions 1/5

What is the primary mental shift when moving from class components to functional components with hooks?

Quiz Questions 2/5

To run a side effect only once after the component's initial render, similar to componentDidMount, what should you pass as the second argument to useEffect?