Mastering the useEffect Hook in React

Mastering the useEffect Hook in React

Introduction to the useEffect hook

The useEffect hook is a function that allows you to perform side effects in function components. It is a hook that is called after every render of the component, including the first render. This makes it a great place to perform tasks that are related to the component, such as fetching data or updating the document title.

The useEffect hook is called with a function as its argument. This function is called the "effect", and it is where you can perform your side effects. The effect can be synchronous or asynchronous, and it can return a clean-up function to be called when the component is unmounted.

Basic usage of the useEffect hook

import { useEffect } from 'react'

function Example() {
  useEffect(() => {
    // Perform some side effect
  },[]) 
}

In the example above, the useEffect hook is called with an empty array. This means that the side effect will be performed once only.

Adding dependencies to the useEffect hook

By default, the useEffect hook is called after every render of the component. However, you can tell the useEffect hook to only perform the side effect when certain values have changed by providing an array of dependencies as the second argument.

The dependencies are values that the effect depends on. If any of the dependencies change, the effect will be called again. If none of the dependencies change, the effect will not be called again.

Empty array of dependencies

import { useEffect } from 'react'

function Example() {
  useEffect(() => {
    // Perform some side effect
  }, []) // Do not perform the side effect if any values change
}

In the example above, the side effect will only be performed once, on the first render of the component. It will not be performed again, even if the component re-renders.

This is useful for performing tasks that should only be done once, such as setting up an event listener or fetching data.

One dependency

import { useEffect, useState } from 'react'

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

  useEffect(() => {
    // Perform some side effect
  }, [count]) // Only perform the side effect if the count value changes
}

In the example above, the side effect will be performed every time the count value changes. If any other values change, the side effect will not be performed.

This is useful for performing tasks that depend on a specific value, such as updating the document title or fetching data based on a search query.

No dependencies

import { useEffect } from 'react'

function Example() {
  useEffect(() => {
    // Perform some side effect
  }) // Perform the side effect after every render
}

In the example above, the side effect will be performed after every render of the component. This includes the first render, as well as any subsequent re-renders.

This is useful for performing tasks that should be done after every render, such as updating the document title or fetching data.

Cleaning up after the useEffect hook

Sometimes, you may need to perform a clean up after the useEffect hook has been called. For example, if you are setting up an event listener, you will need to remove the event listener when the component is unmounted. You can do this by returning a function from the useEffect hook:

import { useEffect } from 'react'

function Example() {
  useEffect(() => {
    const handleClick = () => {
      // Do something when the document is clicked
    }
    document.addEventListener('click', handleClick)

    // Return a function to clean up after the useEffect hook
    return () => {
      document.removeEventListener('click', handleClick)
    }
  })
}

In the example above, the event listener is set up when the component is rendered, and it is cleaned up when the component is unmounted.

The clean-up function is called before the component is unmounted, and before the next effect is called. It is a good place to perform any clean-up tasks that are related to the effect, such as canceling network requests or removing event listeners.

Real-life use cases

Fetching data

import { useEffect, useState } from 'react'

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

  useEffect(() => {
    // Fetch the user data when the component is rendered
    async function fetchUser() {
      const response = await fetch(`/api/users/${userId}`)
      const data = await response.json()
      setUser(data)
    }
    fetchUser()
  }, [userId]) // Only fetch the user data if the userId changes

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

  return <p>{user.name}</p>
}

In the example above, we have a component that displays the name of a user. The component fetches the user data from an API when it is rendered, and it only fetches the data again if the userId value changes. This is by far the most common use of useEffect.

Updating the document title

import { useEffect, useState } from 'react'

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

  useEffect(() => {
    // Update the document title after every render
    document.title = `You clicked ${count} times`
  })

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

In the example above, the useEffect hook updates the document title after every render of the component. The title reflects the current value of the count state.

Adding and removing event listeners

import { useEffect } from 'react'

function Example() {
  useEffect(() => {
    const handleClick = () => {
      // Do something when the document is clicked
    }
    document.addEventListener('click', handleClick)

    // Return a function to clean up after the useEffect hook
    return () => {
      document.removeEventListener('click', handleClick)
    }
  })
}

In the example above, the useEffect hook sets up an event listener when the component is rendered, and it removes the event listener when the component is unmounted.

Unusual Behaviour in React v18

A significant change that broke things was introduced in React 18: while Strict Mode is active, all components mount and unmount before being remounted again.

This means that each component is mounted, then unmounted, and then mounted again.

We can confirm the behaviour by using the cleanup function of the useEffect hook.

useEffect(() => {
  console.log("Mounted");//Shown twice in output

  return () => console.log("Cleanup");
}, []);

This only occurs in the development mode; it does not occur in the production mode. The only way to get rid of this is to disable StrictMode in our App, but it is not recommended to do so.

This was introduced so that in the future, when React decides to offer a feature that allows it to add or delete an area of the UI while still maintaining the state, this will help it do so. For instance, when moving between tabs, maintaining the state of the preceding tab might assist avoid the execution of effects such as API calls that are not essential.

You can read more about this at github.com/facebook/react/issues/24502

Conclusion and further readings

The useEffect hook is a powerful tool for performing side effects in function components. It can be used to fetch data, update the document title, and set up or clean up event listeners. By providing an array of dependencies, you can control when the effect is called, and by returning a clean-up function, you can ensure that any necessary clean-up tasks are performed.

https://beta.reactjs.org/reference/react/useEffect

https://www.freecodecamp.org/news/react-useeffect-absolute-beginners/

https://www.youtube.com/watch?v=dH6i3GurZW8

Did you find this article valuable?

Support Divij Sehgal by becoming a sponsor. Any amount is appreciated!