August 26, 2020

Mental Model of Use Effect

or Don't Use the Dependencies Array as a Conditional
edit

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.


Liked the post?
Give the author a dopamine boost with a few "beard strokes". Click the beard up to 50 times to show your appreciation.
Kyle Shevlin's face, which is mostly a beard with eyes

Kyle Shevlin is the founder & lead software engineer of Agathist, a software development firm with a mission to build good software with good people.

Logo for Just Enough Functional Programming
Just Enough Functional Programming
Check out my courses!
If you enjoy my posts, you might enjoy my courses, too. Click the button to view the course or go to Courses for more information.
Sign up for my newsletter
Let's chat some more about TypeScript, React, and frontend web development. Unsubscribe at any time.