Before talking about the useEffect
Hook, let me ask you: Have you ever tried to do something like:
document.getElementById("someId").classList.add("someClass");
and then found out that React was now complaining TypeError: Cannot read property 'classList' of null
?
Well, I most certainly have.
At first, I would be like, "What’s going on now? I have a div element with someId
just right there!!!" And then I would realize, "Aha, React has not finished positioning all my HTML elements on the page yet. It does not know about the div with someId
yet. So my div is null to React."
Now, let's talk about useEffect()
.
When I think of useEffect()
, I imagine it as the first door that React knocks on, the moment it is done with positioning all HTML elements on the page. And therefore, it is where we should put the code that we want to run right after that happens so that React never complains about missing elements.
Since React renders every time a change happens to a component, it knocks on/calls useEffect()
after every render.
The simplest structure of useEffect
looks like this:
useEffect( ()=>{
// code to run after every render/re-render
});
Let's see a simple example.
import React {useState, useEffect} from 'react';
const App =()=> {
let [circleBackgroundColor, setCircleBackgroundColor] = useState("lightblue");
useEffect(()=>{
// change the bg-color of the body after every render
document.body.style.backgroundColor = getRandomColor();
})
const changeCircleBackgroundColor = ()=>{
// change the value of circleBackgroundColor
setCircleBackgroundColor(getRandomColor())
}
return(
<main>
<div
style={{backgroundColor:circleBackgroundColor}}
className="circle"
/>
<button onClick={changeCircleBackgroundColor}>Change Color</button>
</main>
)
}
const getRandomColor = ()=>{
return "#" + Math.random().toString(16).slice(2,8);
}
Notice here that the bg-color of body
changes not only on refreshing the page but also on changing the background color of the circle div.
Why does that happen?
On refreshing the page, the bg-color of body
changes because React calls useEffect
right after the first render.
On changing the bg-color of the circle div, the bg-color of body
changes because when changing the state variable circleBackgroundColor
, React has to re-render App
. And after re-rendering, it calls useEffect
again.
But wait, is that even efficient?
I mean, calling useEffect
every time a tiny, little change happens to a component, is that efficient?
No, it is not. And most of the time, we would not want that to happen.
useEffect
takes a second optional parameter called the dependency array. We pass the dependency array to useEffect
to tell React not to bother calling useEffect
every time a change happens to a component. To tell React that it just needs to keep an eye on the dependency array variables and that it needs to call useEffect
only when any of these variables change.
So that's how useEffect
should look like now:
useEffect(()=>{
// code to run only on the first render and when depency1 or/and depency2 changes.
},
// the dependency array
[depency1, depency2]);
Let's make a few changes to our last example and pass a dependency array to useEffect
.
...
// onOff will be a dependency to useEffect
let [onOff, setOnOff] = React.useState(true);
...
useEffect(()=>{
document.body.style.backgroundColor = getRandomColor();
// add onOff in the dependency array
}, [onOff])
}
...
// just a button to toggle the value of onOff
<button onClick={()=>setOnOff(!onOff)}>toggle onOff</button>
If a component has useEffect
, the first thing React does after the first render is to call useEffect
-- whether it has a dependency array or not. After that, React gets to know if useEffect
has a dependency array or not. And based on that knowledge React has to, well, react.
If there is not a dependency array passed to useEffect
, React has to remember to call useEffect
after all future renders.
If there is a dependency array passed to useEffect
, React has to keep an eye on all these dependencies. And after any future changes happen to any of them, React will have to call useEffect
.
Let's see that in code.
In this example, when React calls useEffect
after the first render, it knows that there is an onOff
dependency passed to it. So, it will then remember that it will not have to call useEffect
unless onOff
changes.
Hence, the bg-color of body
changes only on the first render and every time onOff
changes.
Notice that changing the bg-color of the circle div
does not affect body
anymore. React no longer calls useEffect
after changing circleBackgroundColor
.
So now, we have limited the calls to useEffect
from "after every render/re-render" to "only after the first render and all the re-renders caused by the changes of the dependencies."
What if we want to limit the calls to even less than that?
What if we want to run useEffect
only once, after the first render?
We can achieve that by passing an empty array as the dependency array to useEffect
. By doing that, React will call useEffect
after the first render and then forget all about it. It will never call it again.
Note: You have to be careful when using []
as the dependency array. Make sure that useEffect
does not contain any variables (state
or props
) or functions from inside its component. Refactor and strip useEffect
of all the variables and functions from inside its component before using []
as the dependency array, or you have to list them as dependencies.
One last thing to mention on this subject is that we can return a function at the end of useEffect
. That function runs after every re-render (just right before the new effect runs), to clean up after the effect from the last render. It also runs right before the component is removed from the DOM.
If we change a little bit in the code form the earlier example:
useEffect(()=>{
// add a delay to see the effect of the clean-up function
setTimeout(()=>{
document.body.style.backgroundColor = getRandomColor()
}
, 500);
// the clean up function
return ()=>{
// it cleans up the bg-color of the body
// just right before the new effect tasks a place
// and changes it again
document.body.style.backgroundColor = "white";
}
}, [onOff])
Here, when we toggle onOff
, the cleanup function runs to clean up the bg-color of body
before the new effect runs and changes it again.
That's it.
Thank you for reading.
Top comments (2)
I do wonder why you're no longer adding a class to an element in your examples. I might be wrong, but your examples don't answer the question in your first paragraph: how do you add a class (via classList.add) to an element that hasn't yet been found by React?
Yeah, I didn't use an example to answer that question (sorry). However, I think what I wanted to say was that if we wanted to add some code that depended on the component's render,
useEffect
would be the right place to add that code. Because it gets called after all the elements are painted on the page. :)