React useEffect Hook in Depth

Caelin Sutch
5 min readDec 21, 2020

Since React hooks were introduced in React v16, developers have jumped on using them in their applications due to their simple syntax and power. useEffect is one of the most popular hooks, but it has a surprising amount of subtlety that’s easy to miss.

To make this simple, we’ll be looking at a very simple React component and all the different ways useEffect can be used. Please feel free to code along yourself.

Photo by Artem Sapegin on Unsplash

What is useEffect?

At its core, useEffect is a React lifecycle hook, it performs functions at certain points in the lifecycle of a React component.

It replaces the classic React functions componentDidMount , componentDidUpdate , and componentWillUnmount with a shorter and simpler syntax.

With this hook, developers could finally modify state directly in functional components, whereas before, we were strictly banned from creating side-effects inside of the render method (or a functional component).

Lets take a look at a very simple component and see how to use this hook:

We can see here that we’ve created a really basic functional component called Simple that implements the useEffect hook to log an output. If we run this component, you’ll see Use Effect printed once.

This is the most basic version of useEffect, to do something when a function renders. Lets see if this renders just on initial render, or with every re-render as well by creating a wrapper around this Simple component:

Now, we’re using the Simple component and implementing useState in this wrapper to keep track of a counter. Open up the console logs and click the button, you should see “Use Effect” be logged to the console every time the button is clicked. This means that useEffect is ran every time the component is rendered.

Component Unmount

Now, let’s check out the unmount behavior by modifying this wrapper to mount and unmount the component.

Now, we’re telling React to keep rendering the component until the count is less than five. This means, once we’ve clicked the button 5 times the component will unmount. We should see 5 “Use Effects” logged to the console, one when the component is first rendered, the one for each button click, meaning that the useEffect doesn’t currently run on unmount.

Well implementing unmount handling is relatively simple, lets modify our simple component to do that:

Now, we should see “Unmount” being printed every time the component is unmounted (which is every time the button is clicked) a total of 5 times before the Example component is gone.

The console will look like this:

Use Effect!
Unmount
Use Effect!
Unmount
Use Effect!
Unmount
Use Effect!
Unmount
Use Effect!
Unmount

This is a great way to visualize the component lifecycle, the mount and unmount behavior runs every time the component is re-rendered, which is every time the button is clicked.

The key takeaway here is that mount and unmount behavior comes in pairs.

Lets modify our Simple component to see in action when a property is passed in (count):

Then, make sure you modify the Wrapper component to use the count prop:

<Example count={count} />

This will result in the following output:

Render - 0
Unmount - 0
Render - 1
Unmount - 1
Render - 2
Unmount - 2
Render - 3
Unmount - 3
Render - 4
Unmount - 4

This shows shows again how the “pair” of mount and unmounts behave, they both act on the same state of the component and run each time the component is updated.

Note how the unmount always has the same data as it was mounted with. This is actually a really good design choice, as it allows us to cleanup with the same props that were passed in.

Lets say we had a service that subscribed to some data using some ID:

subscribe(id) {}
unSubscribe(id) {}

This requires us to subscribe and unsubscribe to prevent data leaks in our application. If we did this in our useEffect hook, it might look like this:

useEffect(() => {
subscribe(id)
return () => unSubscribe(id)
})

This architecture makes writing safe code easier, we can rest assured that we will always be able to do lifecycle behavior in a really safe way.

Controlling useEffect Calls

Now what we have is fine and dandy, but how do we control when these callbacks are ran? For example, how do we run an effect only at the very beginning of the component, or only after it unmounts for the last time?

These answers and more are part of the last part of the useEffect function, the dependencies array. These values are compared to the previous versions of these values to decide whether or not the component should rerender. This means that if the values change, the useEffect callback will be called.

Lets edit our Simple component again to reflect this:

Now, we’re telling useEffect to observe the count variable for changes, and only run if count changes between renders by putting [count] , or our “dependency array”, as one of the useEffect function arguments.

Lets edit the Wrapper component to see this in action:

Notice how for one of the Simple components, we’re passing in a static number, -1 , instead of the count variable.

Lets look at the difference in the output of these two structures:

render - 0
render - -1 // this was logged by the second component
unmount - 0
render - 1
unmount - 1
render - 2
unmount - 2
render - 3
unmount - 3
render - 4
unmount - 4
unmount - -1 // this was logged by the second component

Now, we can see that the second component useEffect is only called when the component is originally mounted and unmounted. This is because once we put count in the dependency array, useEffect is only called on mount, unmount, and when count changes between renders.

Normally, you’d include multiple values inside of this dependency array, for example if we wanted to fetch some data from our backend everytime we had a prop id change we would put id inside the dependencies array.

We can also use multiple useEffect hooks to watch different variables and act on them differently.

If you just want the hook to run on original mount and unmount, just use an empty dependency array.

Conclusion

That was a lot of information, so lets recap what we learned.

  • useEffect is a React hook to make side effects in functional components.
  • We can use mount and unmount behavior in React hooks to run code at specific lifecycle times in the component.
  • We can return a callback function to run code on component unmount
  • The dependencies array in useEffect can be used to specify when the callback should be run, only when there are changes in the variables in the dependency array.
  • To only trigger useEffect on original mount and unmount, use an empty dependency array

I hope this article helped you better understand how to use useEffect to perform actions during the React component lifecycle!

Keep in Touch

There’s a lot of content out there, I appreciate you reading mine. I’m a young entrepreneur and I write about software development and my experience running and growing companies. You can signup for my newsletter here

Feel free to reach out and connect with me on Linkedin or Twitter.

--

--

Caelin Sutch

Founder, engineer, designer. Passionate about building cool shit.