Optimize useEffect by using a condition as a dependency

rangeoshun

Abel Varga

Posted on April 3, 2020

Optimize useEffect by using a condition as a dependency

The useEffect hook is a great way to wrap side effects since the release of React 16.8. To quickly recap, this hook allows you to create functional components with behaviour akin to React classes' componentDidMount and componentDidUpdate.

But because of the way functional components work, this would mean performing a side effect on each render. This behaviour is suboptimal; hence dependency array was introduced. This array passed as the second argument of a useEffect call allows React to perform only when a reference passed in the dependency array changes.

While this is great in many cases when you need to perform a specific task when one of the dependencies changes, there are times when the reference update in itself does not mean the task in hand is needed. These are the cases when you add a condition to the function passed as the first argument. For example, when you want to update the state when a query is running. You want to run the effect when the query is running, but you do not need to run it when the state is already set to the correct value.

const { loading } = useQuery(query, { /*... */ })
const [state, setState] = useState('initial');

useEffect(() => {
  if (loading && state !== 'loading') setState('loading')
})

You cannot conditionally call useEffect or any other hook for that matter. In these cases, you add the condition in the function performing the side effect itself, while the variables checked in the condition go into the dependency array.

useEffect(() => {
  if (loading && state !== 'loading') setState('loading')
}, [loading, state])

Now to optimize even further, you need to think about what React is doing in the background. When you call useEffect, React checks your dependencies one-by-one. It has to, to be sure that your side effect takes place when any of the dependencies change. After this, your function runs and evaluates the condition it needs to. If it turns out that it needs to perform what it has to it's excellent, no time wasted. On the other hand, when it turns out, it does not have to perform anything, all React's work was in vain.

In the latter scenario, you can consider passing the condition evaluated right away. This way React only needs to check the one value in your dependencies, a boolean, and you already have the value ready for your function to decide whether it should perform or not.

const shouldSetStateToLoading = loading && state !== 'loading'
useEffect(() => {
  if (shouldSetStateToLoading) setState('loading')
}, [shouldSetStateToLoading])

The only caveat is that the condition itself needs to be cheaper then what React does. The odds are that it is.

You can check out the fiddle benchmark below:
https://jsfiddle.net/rangeoshun/j5a7qpbL/

💖 💪 🙅 🚩
rangeoshun
Abel Varga

Posted on April 3, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related