Cleanup Functions in React’s UseEffect Hook — Explained

Written by ljaviertovar | Published 2022/12/01
Tech Story Tags: reactjs | react-tutorial | javascript | javascript-tutorial | programming | frontend-development | web-development | react-hooks

TLDRIf you are starting to learn React or already have some time using this library, surely, you have come across some errors or warnings related to asynchronous functions, especially using the hook useEffect. When I was learning the functionality of this hook, I could not understand the reason to use the return in this function since in most cases it is not necessary to use it and React works perfectly well without it.via the TL;DR App

with examples.

If you are starting to learn React or already have some time using this library, surely, you have come across some errors or warnings related to asynchronous functions, especially using the hook useEffect.

When I was learning the functionality of this hook, I could not understand the reason to use the return in this function since in most cases it is not necessary to use it, and React works perfectly well without it.

As I became more familiar with the way React works and the life cycle of the components, I began to notice that in many cases, it is too important to use the return in the hook useEffect, especially in the side effects.

What are the side effects?

A side effect can be fetching data from a remote server, reading or writing to local storage, setting up event listeners, or setting up a subscription. These side effects can occur when a button is clicked, a form is submitted, or when a component is mounted and unmounted.

React’s useEffecthook allows functional components to do things when a component is mounted or when some properties or states change. This hook also allows cleaning up when the component is unmounted.

Why clean up side effects?

Handling side effects in React is a task of medium complexity. However, from time to time, you may have difficulties at the intersection of the component lifecycle (initial rendering, assembly, usage, disassembly) and the side-effect lifecycle (started, in progress, complete).

One such difficulty is when a side effect completes and attempts to update the state of an already disassembled component.

This causes a React warning like this:

Memory leaks in React applications are mainly the result of not canceling subscriptions made when a component was mounted before the component is unmounted.

They cause many problems, including:

  • Affects the performance of the project by reducing the amount of memory available.
  • Slowing down the application.
  • System crashes.

Therefore, it’s necessary to eliminate memory leak problems.

What is the useEffect cleanup function?

It is a function of the useEffect hook that allows us to stop side effects that no longer need to be executed before our component is unmounted.

useEffectis built in such a way that we can return a function inside it and this return function is where the cleanup happens.

For example, Component A requests the API to get a list of products, but while making that asynchronous request, Component A is removed from the DOM (it’s unmounted). There is no need to complete that asynchronous request.

So as a cleanup method to improve your application, you can clean up (cancel) the asynchronous request so that it’s not completed.

Cleanup function of useEffect:

useEffect(() => {
  // Your effect
  return () => {
    // Cleanup
  }
}, [input])

Cleaning up an effect

Canceling a fetch request

There are different ways to cancel fetch request calls, we can use fetch AbortControlleror Axios AbortController.

To use AbortController, we must create a controller using the AbortController() constructor. Then, when our fetch request initiates, we pass AbortSignal as an option inside the request’s options object.

This associates the controller and signal with the fetch request and lets us cancel it anytime using AbortController.abort():

useEffect(() => {
    //create a controller
    let controller = new AbortController();
    (async () => {
      try {
        const response = await fetch(API,
          {
            // connect the controller with the fetch request
            signal: controller.signal,
          },
        );
        // handle success
        setList(await response.json());
        // remove the controller
        controller = null;
      } catch (e) {
        // Handle the error
      }
    })();
    //aborts the request when the component umounts
    return () => controller?.abort();
},[]);

useEffect(() => {
  // create a controller
  const controller = new AbortController();
  axios
    .get(API, {
      signal: controller.signal
    })
    .then({data}) => {
      // handle success
      setList(data);
    })
    .catch((err) => {
     // Handle the error
    });
    //aborts the request when the component umounts
    return () => controller?.abort();
}, []);

Cleaning up Timeouts

When using setTimeout(callback, time) timer functions, we can clear them on unmount by using the special clearTimeout(timerId) function.

useEffect(() => {
    let timerId = setTimeout(() => {
      // do something     
      timerId = null;
    }, 3000);
  
  // cleanup the timmer when component unmout
    return () => clearTimeout(timerId);
  }, []);

Cleaning up Intervals

Like the Timeouts, the setIntervals(callback, time)have a special function to clean them up with clearInterval(intervalId) function.

useEffect(() => {
    let intervalId = setInterval(() => {
      // do something like update the state
    }, 1000);

    // cleanup the timer when component unmout
    return () => clearInterval(interval);
  }, []);

Cleaning up Event Listeners

Clean up Listeners happens via window.removeEventListener. The removeEventListener call must reference the same function in the removeEventListener call to remove the listener correctly.

useEffect(() => {
  // function to add to EventListener
  const handleKeyUp= (event) => {
    switch (event.key) {
      case "Escape":
        setCollapsed(true);
        break;
    }
  }

  window.addEventListener("keyup", handleKeyUp);
  
  // cleanup the listener when component unmout
  return () => window.removeEventListener("keyup", handleKeyUp);
}, []);

Cleaning up Web Sockets

When you create a WebSocket connection, you can close it in the cleanup socket.close()function.

useEffect(() => {
  const ws = new WebSocket(url, protocols)
  // do what you want with the socket
  ws.onmessage = (event) => {
    setValue(JSON.parse(event.data));
  };
  
  // cleanup the web socket when component unmout
  return () => ws.close()
}, [])

Conclusion

We have learned that some side effects require cleanup to avoid memory leaks and unnecessary and unwanted behaviors. We must learn when and how to use the cleanup function of the useEffect hook to avoid these problems and optimize applications.

I recommend cleaning up asynchronous effects when the component is unmounted. Also, if the asynchronous side effect depends on the prop or state values then consider also cleaning them up when the component is updated.

I hope you found this article useful and that you can now use the cleanup feature correctly.

Read more:

https://hackernoon.com/autocomplete-search-component-with-react-and-typescript?embedable=true

Also published here.


Written by ljaviertovar | ☕Web Developer |🤖Automatization and Web Scraping enthusiast |🚀Lover of sci-fi
Published by HackerNoon on 2022/12/01