The useEffect Hook

With the useEffect hook, you can bind actions to lifecycle events inside function components. Namely, three of them: componentDidMount, componentDidUpdate, and componentWillUnmount. All with one function!

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!

In class component:

resetCount = () => {
  this.setState(
    { count: 0 }, 
    () => console.log("Count is updated to ", count);
  )
}

In function component:

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

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

Notice you cannot provide a second call back function argument to setCount. This is a departure from how this.setState works in class components.

Resources