The useEffect Hook

With the useEffect hook, you can bind actions to lifecycle events inside function components.

In React, every component has a lifecycle! From when it is instantiated to when it is unmounted from the DOM. We can hook up to each stage by employing special methods and performing specific actions at different stages of each component's lifecycle.

Background: The lifecycle methods were introduced in the React Component class. React would automatically call these methods at certain times during the "life" of a (class) Component. Here are the most commonly used ones (but there are more):

  • componentDidMount(): invoked immediately after the component is inserted into the DOM.
  • componentDidUpdate(): invoked immediately after updating occurs. This method is not called for the initial render.
  • componentWillUnmount(): invoked immediately before a component is removed from the DOM.

The useEffect hook replaces all above three functions!

useEffect as componentDidMount

In class component:

componentDidMount() {
  this.fetchDataFromExternalAPI();
}

In function component:

useEffect(() => fetchDataFromExternalAPI(), []);

Notice the general syntax of useEffect is:

useEffect(effectFunction, arrayDependencies);

If you want the useEffect to behave as componentDidMount, you must provide an empty array ([]) as its dependency:

useEffect(() => {
  console.log("Run only for first render, when component is mounted!");
}, []);

Unlike componentDidMount, the useEffect itself is not asynchronous. If you want to use async/await pattern, you should follow an approach similar to this:

useEffect(() => {
  async function fetchData() {
    const response = await axios(url);
    console.log(response.data);
  }  

  fetchData();
}, []);

useEffect as componentWillUnmount

According to React's Docs, componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Therefore, you should use it to perform any necessary cleanup in this method, such as canceling network requests or cleaning up any subscriptions that were created in componentDidMount().

componentDidMount() {
  this.signInToChat();
}

componentWillUnmount() {
  this.signOutOfChat();
}

In function component:

useEffect(() => {
  signInToChat();
  return () => signOutOfChat();
}, []);

Above, I'm returning a function from the "effect function." This returned function will be called to clean up the effect.

So, the useEffect, given an empty dependency array, can replace two lifecycle methods!

useEffect(() => {
  console.log("Component did mount!");

  return () => {
    console.log("Component will unmount!");
  }
}, []);

useEffect as componentDidUpdate

Recall that every change to state (or props) will cause a component to re-render. On every render, useEffect will have a chance to run. That is, by default, your effects will execute on every render. Still, you can limit how often they run by providing the dependency array.

If you don't provide a dependency array:

useEffect(() => {
  console.log("Component did update!");
  console.log("I run on every re-render!");
});

Often, you'll only want an effect to run in response to a specific change. For example, maybe a prop's value changed or a change occurred to state. That's what the second argument of useEffect is for: it's a list of "dependencies."

useEffect(() => {
  console.log(`I only run when ${count} changes!`);
}, [count]);

This latter pattern is also helpful for debugging:

useEffect(() => {
  console.log("Count is updated to ", count);
}, [count]);

const resetCount = () => setCount(0);
Resources