useEffect

The fewer raw useEffect calls you have in your components, the easier you will find to maintain your application.

  • https://react.dev/learn/you-might-not-need-an-effect

Side Effects

A side effect is a term in functional programming that refers to when a function relies on, or modifies, something outside its "responsibility" to do something. i.e Modifying a variable or any object outside the function's scope; Printing to the console; Making network requests; etc.

In the context of React, side effects include anything that isn't "predictable" or is external to React such as fetching data from an API, interacting with browser APIs, using setTimeout/setInterval, etc. React wants us to isolate such operations from the pure rendering of a functional component using the useEffect hook.

The useEffect hook

useEffect is a powerful but often misused hook.

This hook is typically used for:

  • making API requests (without a library) for data that your app will display when it first mounts

  • sending any "fire and forget" network requests like a POST request to an analytics service to log that a component was viewed

  • to synchronise with things outside React: browser APIs like the DOM, WebGL, Canvas, Video, and Audio and third-party widgets/libraries like D3.js, Google Charts, Maps, etc.

  • initialising a connection to a streaming/websocket server such as for a live chat component

useEffect can be used in three ways (note the difference in the 2nd argument):

useEffect(() => {}, [])

empty [] means this will only run once when the Component mounts

useEffect(() => {}, [someValueToMonitor])

run on mount and then only if someValueToMonitor has changed

useEffect(() => {})

no 2nd argument means this will run on every render/rerender

APIs and useEffect

Fetching data from an API when a component mounts is one primary use case for useEffect if you're not using a library or an abstraction for data fetching and state management like tanstack query or swr.

One important thing to note is that the callback function you pass to useEffect cannot return a Promise and therefore cannot be an async function. You can define and/or call an async function inside the callback though.

Don't do this:

const [quotes, setQuotes] = useState([]);

useEffect(async () => {
  const res = await fetch('https://dummyjson.com/quotes');
  const { quotes } = await res.json();
  setQuotes(quotes);
}, []);

Do this:

const [quotes, setQuotes] = useState([]);

useEffect(() => {
  const getQuotes = async () => {
    const res = await fetch('https://dummyjson.com/quotes');
    const { quotes } = await res.json();
    setQuotes(quotes);
  }

  getQuotes();
}, []);

Cleanup Function

The reason your useEffect callback can't return a Promise is that the hook expects a plain function as an optional return value. This returned function acts as a cleanup function and will be executed after a rerender and after the component unmounts. More detail here.

Let's say your app needs to know when the browser window is resized. The window is part of the DOM which is an external system to React so we add the event listener in a useEffect:

useEffect(() => {
  const handleResize = () => {
    console.log("window resized");
  };
  window.addEventListener("resize", handleResize);
}, []);

The problem with the code above is that when the component the useEffect is in is umounted, the resize event listener will still be attached to the window and will keep firing. The implications of that depends on the application but can range from inefficient to disastrous. That's what the cleanup function is for.

Remove the resize event listener when the component is unmounted:

useEffect(() => {
  const handleResize = () => {
    console.log("window resized");
  };
  window.addEventListener("resize", handleResize);

  return () => window.removeEventListener("resize", handleResize);
}, []);

Last updated