How to use the React useEffect Hook
© https://reactjs.org/

How to use the React useEffect Hook

The useEffect Hook lets you perform side effects in function components.

ByMario Kandut

honey pot logo

Europe’s developer-focused job platform

Let companies apply to you

Developer-focused, salary and tech stack upfront.

Just one profile, no job applications!

Hooks are available in React, since v16.8 (2018) and enable function components to manage state and side effects. They work side-by-side with existing code. and have a lot of other great features, check out the Intro to React Hooks blog post.

React provides a few built-in Hooks like useState and useEffect. This blog post is about the useEffect hook, for more information about the React useState Hook checkout this blog post How to use the useState hook.

useEffect

💰 The Pragmatic Programmer: journey to mastery. 💰 One of the best books in software development, sold over 200,000 times.

The Effect Hook lets you perform side effects in function components. Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. They can also be just called effects.

The useEffect Hook can be understood as componentDidMount, componentDidUpdate, and componentWillUnmount combined in the React class lifecycle methods.

There are two different types of side effects in React components:

  • those that don’t require cleanup, and
  • those that do.

Effects without Cleanup

Some examples of effects that don’t require a cleanup are network requests, manual DOM mutations, and logging. We can run them and immediately forget about them.

Let's have a look on how class components and function components can handle these type of side effects.

The following example is based on the counter example from the useState hook blog post. Let's say we want to update the document title after React updates the DOM:

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button
          onClick={() =>
            this.setState({ count: this.state.count + 1 })
          }
        >
          Click me
        </button>
      </div>
    );
  }
}

In React classes, side effects are in life cycle states, in this case in componentDidMount and componentDidUpdate. As you can see in the code example above, there is code duplication. In many cases we want to update a component who did just mount or has been updated, basically after every render.

The same use case with using React Hooks:

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

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

With the useEffect Hook you tell React that your component needs to do something after rendering. React will call the effect after performing DOM updates.

The useEffect Hook is placed inside the component to access the state (count variable) right from the effect, without any additional APIs, it's already in scope.

Hooks embrace JavaScript closures and avoid introducing React-specific APIs where JavaScript already provides a solution.

The useEffect hook will run on every render. If you come from React classes, don't think like mount or unmount, think of useEffect like after rendering.

When you take a detailed look at the useEffect Hook, you will see that the function passed into it changes on every render. This is intentional and, we don't have to worry about count getting stale. Every time we re-render , we schedule a different effect, replacing the previous one.

Effects scheduled with useEffect don't block the browser from updating the screen, componentDidMount or componentDidUpdate do.

Effects with Cleanup

The other type of effects, are effects which require a cleanup. This could be a subscription to some external data source. If we don't clean up after subscribing we would introduce a memory leak in our application.

The React docs also have a great example for this, which I am going to utilize below. Let’s say we have a ChatAPI module that lets us subscribe to a friend’s online status, and we compare using Classes and using Hooks.

In a React Class component, you would typically set up a subscription in componentDidMount, and clean it up in componentWillUnmount.

class FriendStatus extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isOnline: null };
    this.handleStatusChange = this.handleStatusChange.bind(this);
  }

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange,
    );
  }
  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange,
    );
  }
  handleStatusChange(status) {
    this.setState({
      isOnline: status.isOnline,
    });
  }

  render() {
    if (this.state.isOnline === null) {
      return 'Loading...';
    }
    return this.state.isOnline ? 'Online' : 'Offline';
  }
}

The lifecycle methods componentDidMount and componentWillUnmount need to mirror each other. Lifecycle methods force us to split this logic even though conceptually code in both of them is related to the same effect.

In a React Function Component with the useEffect Hook the code for adding and removing a subscription is so tightly related that useEffect is designed to keep it together. If your effect returns a function, React will run it when it is time to clean up.

With the useEffect Hook it could be written like this:

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

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(
      props.friend.id,
      handleStatusChange,
    );
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(
        props.friend.id,
        handleStatusChange,
      );
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

Every effect may return a function that cleans up after it. This lets us keep the logic for adding and removing subscriptions close to each other, and they're just part of the same effect.

React performs the cleanup when the component unmounts. However, as we learned earlier, effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.

TL;DR

  • The Effect Hook lets you perform side effects in function components.
  • There are two different types of useEffect hooks, with cleanup and without.

Thanks for reading and if you have any questions, use the comment function or send me a message @mariokandut. If you want to know more about React, have a look at these React Tutorials.

References (and Big thanks):

React Hooks, Using the Effect Hook

Scroll to top ↑