React useEffect Hook in Depth
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.
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.