Mental Model of Use Effect
One thing I have found challenging about React.useEffect
is the mental model regarding the dependencies array.
It’s very easy to get it in your head to use the dependency array as a means to constrain the calling of the effect, essentially treating it as a guard or conditional.
”If this value changes, do that effect.”
The problem is that you’ll often run into issues where you only want to run an effect when a single value changes, and thus you’ll be tempted to leave out the other dependencies from the dependency array. Next thing you know, you’ll be facing an ESLint warning about it. Like with the following code:
React.useEffect(() => {
// Pretend these values are in the parent scope
const diff = prevQuantity - quantity
someHandlerFunction(diff)
}, [quantity])
This, more or less, works like we want it to. When quantity
changes, we call the effect and call the handling function. However, this will also warn you that you haven’t added all the dependencies. You might think, “I don’t want this effect to run when the handler function changes! That’ll break the code!”
Trust me, I get stuck in this loop a lot, too.
Here’s what you need to understand. Simply put, not calling the effect whenever a dependency changes means you’ll have stale values and functions. It will lead to bugs.
Instead, you must make the mental shift to:
Call this effect whenever the dependencies change, but add a conditional in the effect function that only triggers the changes you need, when you need them to happen.
The more correct code looks like this:
React.useEffect(() => {
if (prevQuantity !== quantity) {
const diff = prevQuantity - quantity
someHandlerFunction(diff)
}
}, [prevQuantity, quantity, someHandlerFunction])
Now we can call the effect whenever a dependency changes, while still not calling our handler function when we don’t intend to.
Fixing this small mental model issue helps you avoid the ESLint warnings and write better code. Hope this helps you.
Update - August 27, 2020
I had an additional thought regarding this topic and wanted to add it to the post. Another way to think about the useEffect
hook is to write the code as if the hook and the dependency array wasn’t there first, and then wrap it in the hook and add the dependencies later.
If I think about this outside of the hook, my mental model for the functionality is, “If there’s a difference between these two values, send the difference to this handling function.” When we write that code, it looks like the body of our effect, but it makes sense in isolation.
if (prevQuantity !== quantity) {
const diff = prevQuantity - quantity
someHandlerFunction(diff)
}
Now that we’ve written the functionality we desire, the next step is to put React in charge of tracking all of these values for us.
// highlight-next-line
React.useEffect(() => {
if (prevQuantity !== quantity) {
const diff = prevQuantity - quantity
someHandlerFunction(diff)
}
// highlight-next-line
}, [prevQuantity, quantity, someHandlerFunction])
Hope this little update also helps you in your mental understanding of the useEffect
hook.