useEffect

useEffect should be used for any background process that does not arise as a result of a user interaction or standard state change. The canonical example would be dispatching a request to an API to retrieve data to display in a component’s initial render.

useEffect enshrines but also simplifies the lifecyle methods that are used with class-based React components.

Demonstration

const [workflowTitle, setWorkflowTitle] = useState("");
const [loading, setLoading] = useState(false);

useEffect(() => {
  const fetchWorkflowTitle = () => {
    axios
      .post(`${process.env.REACT_APP_ENDPOINT}/load-workflows`, {
        workflow_id: project_id,
        user_id: isAuth?.userId,
      })
      .then((res) => {
        setWorkflowTitle(res?.data?.meta.name);
      })
      .finally(() => setLoading(false))
      .catch((err) => {
        console.error(err);
      });
  };
  setLoading(true);
  fetchWorkflowTitle();
}, [project_id, isAuth]);

In this example we fetch some data from a REST API and store the response value as state, which can then be accessed within our JSX template.

Function placement

In the example, the function is declared inside of the useEffect hook and then called. We do this to avoid adding the function to the dependency array, which we couldn’t do if we defined it outside of the hook, somewhere in the component. If we did this, the function would run on every render by default and would be recalled again because of its placement in the dependency array, which leads to the ‘too many rerenders’ error.

An alternative would be to define it outside of the component itself but this is difficult since it requires state from the component to run.

Dependency array

Note the array that is the second argument to useEffect. This is the dependency array and is how we manage the lifecycle of the side-effect.

  • After the initial call on render, the function within the useEffect block will run any time one of the dependencies update. The dependency array typically refers to variables that are defined within the hook but can also contain references to variables outside of it.

  • If you do not include the array, the effect runs on every render

  • If you pass an empty array, the effect will only run once on the initial render

Cleanup functions

The syntax of the useEffect hook also allows you to handle cleanup: something you want to do when the component unmounts (another example of how this hook recasts the traditional lifecycle methods) . In addition to running on unmount, the cleanup function will run before the effect runs again (i.e. when it runs in response to a change in one of the elements of the dependency araray). This is chiefly used to prevent memory leaks and the ‘update on unmounted component error’.

You do this by having a return clause after the main effect. Schematically:

useEffect(() => {
  //  Run your effect;
  return () => {
    //   Put your cleanup function here
  };
}, [input]);

Here is an example of using a cleanup to cancel a subscription

useEffect(() => {
  let isApiSubscribed = true;
  axios.get(API).then((response) => {
    if (isApiSubscribed) {
      // handle success
    }
  });
  return () => {
    isApiSubscribed = false;
  };
}, []);

Resources

Understanding the React useEffect cleanup function