Telerik blogs

React Hooks aim to solve the difficulties of logic reuse by enabling us to write components that have access to features like state, context, lifecycle methods, ref, etc. In this article, we’ll focus on the powerful useEffect hook, which allows us to perform side effects in our function components.

The concept of Hooks in React was introduced in early 2019 and for the last few years has been a commonly used utility in the world of React development.

For those who may be new to React, React Hooks aim to solve the difficulties of logic reuse by enabling us to write functional components that have access to features like state, context, lifecycle methods, ref, etc. without having to write a class component.

Many different hooks are immediately available to us when working with React, such as:

  • useState Hook
  • useEffect Hook
  • useContext Hook
  • useRef Hook
  • etc.

React also gives us the capability to create our unique hooks to reuse custom stateful logic between components.

In the future, we’ll spend more time discussing all the different capabilities React Hooks give us, but for this article we’ll direct our attention to the powerful useEffect hook.

useEffect Hook

The useEffect hook allows us to perform side effects in our function components. Side effects are essentially anything where we want an “imperative” action to happen. API calls, updating the DOM, subscribing to event listeners—these are all side effects that we might like a component to undergo at different times.

The useEffect hook doesn’t return any values but instead takes two arguments. The first being required and the second optional.

  • The first argument is the effect callback function we want the hook to run (i.e., the effect itself).
  • The second (optional) argument is a dependency array that contains dependencies that when changed trigger the effect to rerun.

Description of the useEffect Hook

When written in code, the useEffect hook looks like the following:

import React, { useEffect } from "react";

export const FunctionComponent = () => {
  useEffect(() => {
    // effect callback function
  }, [] /* optional dependencies array */);

  return (
    // ...
  );
}

There are three stages of a lifecycle of a React component where we may want to run a side effect:

  • On every render
  • Only on initial render
  • On initial render and anytime a certain dependency changes

Run Effect on Every Render

Assume we wanted to place a console.log() message within an effect callback in a function component.

import React, { useEffect } from "react";

export const FunctionComponent = () => {
  useEffect(() => {
    console.log("run for every component render");
  });

  return (
    // ...
  );
}

By default, the effect stated in a useEffect hook runs when the component first renders and after every update. If we run the above code, we’ll notice the console.log() message is generated as our component is rendered.

Effect running on initial re-render

If our component was to ever re-render (for example due to a component state change), we’ll see the side effect run on every re-render that occurs.

Effect running on component re-render

Run Effect Only on First Render

The second argument of the useEffect hook is optional and is a dependency list that allows us to tell React to skip applying the effect only until in certain conditions. In other words, the second argument of the useEffect hook allows us to limit when the effect is to be run. If we simply place a blank empty array as the second argument, this is how we tell React to only run the effect on initial render.

import React, { useEffect } from "react";

export const FunctionComponent = () => {
  useEffect(() => {
    console.log("run only for first component render (i.e. component mount)");
  }, []);

  return (
    // ...
  );
}

With the above code, the console.log() message will only be fired when the component first mounts and won’t be generated again even if the component was to re-render multiple times.

Effect running only on initial component render

Run Effect on First Render and Rerun When Dependency Changes

Instead of having an effect run once in the beginning and on every update, we can attempt to restrict the effect to run only in the beginning and when a certain dependency changes.

Assume we wanted to fire off a console.log() message every time the value of a state property was to change. We can achieve this by placing the state property as a dependency of the effect callback.

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

export const FunctionComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log(
      "run for first component render and re-run when 'count' changes"
    );
  }, [count]);

  return (
    <button onClick={() => setCount(count + 1)}>
      Click to increment count and trigger effect
    </button>
  );
};

Above, we have a button in the component template responsible for changing the value of the count state property when clicked. Whenever the count state property is changed (i.e., whenever the button is clicked), we’ll notice the effect callback is run and the console.log() message is fired!

If we had a different state property change, we’ll notice the effect callback won’t be rerun and the console.log() message won’t fire since this property isn’t a dependency of the effect.

Effect running on initial component render and dependency change

Run Effect with Cleanup

An effect callback gets run every time on the initial render of a component and when we’ve specified when an effect should run (i.e., when an effect dependency has changed). The useEffect hook also provides the ability to run a cleanup after the effect. This can be done by specifying a return function at the end of our effect.

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

export const FunctionComponent = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("run for every component render");

    return () => {
      console.log("run before the next effect and when component unmounts");
    };
  }, [count]);

  return (
    <button onClick={() => setCount(count + 1)}>
      Click to increment count and trigger effect
    </button>
  );
};

In the example above, we’ll notice the cleanup function message be fired before the intended effect is ever run. In addition, if our component ever unmounts—the cleanup function will run as well.

Effect running with cleanup

A good example of when we might need a cleanup is when we set up a subscription in our effect but want to remove the subscription whenever the next subscription call is to be made, to avoid memory leaks.

Wrap-up

This article covers all the different ways the useEffect hook can be utilized to run side effects in components. At any time we want an “imperative” action to occur in our component (e.g., call an API, subscribe to an event listener, etc.), we should automatically think of leveraging the useEffect hook to run a side effect for a particular moment in the lifecycle of the React component.


About the Author

Hassan Djirdeh

Hassan is currently a senior frontend engineer at Doordash. Prior to Doordash, Hassan worked at Instacart and Shopify, where he helped build large production applications at-scale. Hassan is also a published author and course instructor and has helped thousands of students learn in-depth fronted engineering tools like React, Vue, TypeScript and GraphQL. Hassan’s non-work interests range widely and, when not in front of a computer screen, you can find him at the gym, going for walks or running through the six.

Related Posts

Comments

Comments are disabled in preview mode.