A Quick Walk-Through of useEffect
Adam C. |

ReactJS Docs have a full-page introduction on useEffect. It's a great introduction, and definitely worth your time to read it through. But it's quite a long page, and a bit hard to understand if you don't know the basics. So I would like to provide some quick walk-through before you dive deep into it.

Photo by Fernando Jorge on Unsplash

The Key

useEffect’ as its name suggests, it would apply some ‘effect’ (side effects) to your component, something like fetching data  (data changes → component re-renders,) manipulating the DOM  (changing document title, focusing on the input element, etc.) and doing other user actions like subscription, etc.

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

Usage 1 - Called after componentDidMount & componentDidUpdate

import React from "react";
import { useEffect, useState, memo } from "react";

const useEffectExample = () => {
  const [name, setName] = useState("Adam");

  const handleInputChange = (e) => {
    setName(e.target.value);
  };

  useEffect(() => {
    document.title = name;
  });

  return (
    <div>
      <input value={name} onChange={handleInputChange} />
      <p>Hi {name}</p>
    </div>
  );
};

export default memo(useEffectExample);

This is a perfect example to show useEffect hook being used as componentDidMount and componentDidUpdate combined.

  1. Right after the component's initial render, useEffect hook is called and set the document's title using the default name. 
  2. When the input changes, the “name” state changes, the component re-renders, and then useEffect hook is called again, by setting the document's title to whatever the user enters.  

Usage2 - Called Only after componentDidMount

But sometimes we wanted to useEffect to fetch data at the initial page render ONLY, for example, we pull the states information from API, which does not change as “name” changes. So we only need to pull it once. Let's look at the second example below:

const useEffectExample2 = () => {
  const [name, setName] = useState("Adam");
  const [states, setStates] = useState([]);

  const handleInputChange = (e) => {
    setName(e.target.value);
  };

  useEffect(() => {
  	console.log("Fetching US States Data...");
  	const states = fetch("StatesAPI"); //skip details here
  });

  return (
    <div>
      <input value={name} onChange={handleInputChange} />
      <p>Hi {name}</p>
      <Dropdown options={states} />
    </div>
  );
};

It works, but we see the message “Fetching US States Data…” printed in the console log not just for the initial page renders, but every time we key in the name's field, which is not necessary. To fix this, we could simply add a second parameter, an empty array,  to the useEffect hook, like this:

  useEffect(() => {
  	console.log("Fetching US States Data...");
  	const states = fetch("StatesAPI"); //skip details here
  }, []);

We call the second parameter as a condition array. It won't affect componetDidMount, but for componentDidUpdate, useEffect will check if any member of Array changes, if so, then it will be triggered. Since we passing an empty array, nothing will changes when componentDidUpdate is called, so then no more side effect applied.

Usage3 - Called with Condition

As we mentioned above, the second parameter is an Array, and we could pass in as many conditions as needed. For example, we want to call Get Cities API based on the “state” the user selects. We could add the useEffect hook like this:

   useEffect(() => {
  	console.log("Fetching US States Data...");
  	const cities = fetch("CitiesAPI?state="+stateCode); //skip details here
  }, [stateCode]);

Usage4 - Called Only after componentDidUpdate

Usually, doing the way in Usage 3 is fine: useEffect is called in the initial page renders, which pulls the ‘cities’ data for the default ‘state’. And then when the ‘state’ changes (note that we use stateCode  as a variable name to distinguish the US State from React state,) it will pull the new ‘cities’ data for the US state selected. 

But in some circumstances, we may only want to call useEffect after componentDidUpdate. For example, in the SSR, the initial data fetching could be done on the server-side, then we would like to skip the first data fetching on componentDidMount. Can we do that? Well, useEffect hook does not provide that option, but we could accomplish this by utilizing useRef hook, like this;

// initialize a ref with current value is true
const isMounted = useRef(true)

useEffect(() => {
	if(isMounted.current) { //componentDidMount
		isMounted.current = false;
	} else {  //componentDidUpdate
  		console.log("Fetching US States Data...");
  		const cities = fetch("CitiesAPI?state="+stateCode); //skip details here
  	}
  }, [stateCode]);

Usage 5 - Called after componentWillUnmount

As we mentioned at the beginning of this article, useEffect hook is the combination of componentDidMount, componentDidUpdate, and componentWillUnmount, so the last usage we would like to cover is componentWillUnmount, which we call “Effects with Cleanup”.  For side effects we looked in the previous usages, there is no need to do the cleanup, but in some cases, it is important to clean up so that we don’t introduce a memory leak! For example:

const (width, setWidth) = useState(window.innerWidth);
const hendleResize = () => {
	setWidth(window.innerWidth);
}
useEffect(() => {
	window.addEvenListener('resize', handleResize);
}

Since useEffect hook is triggered after componentDidMount and componentDidUpdate, we would see that the same “resize” listener is added again and again. To fix this, we could add a cleanup function, which will be called before running the effect next time, like this:

const (width, setWidth) = useState(window.innerWidth);
const hendleResize = () => {
	setWidth(window.innerWidth);
}
useEffect(() => {
	window.addEvenListener('resize', handleResize);
	return () => {
		window.removeEventListener('resize');
	}
}

You may notice that useEffect clean up is different from the one using componentWillUnmount, which is only performed when the component unmounts. This is a good thing because it prevents bugs that are common in class components due to missing update logic.

Okay, that's all. Hopefully, it helps you to understand useEffect better.