useEffect or not useEffect?

Igor Snitkin
5 min readSep 24, 2022

Recently I started noticing a new trend online — everyone seemed to trash talk useEffect hook from the notorious React library. It didn’t take very long to find a source for this behavior, a very comprehensive You Might Not Need an Effect article on React Docs.

As we, in my team, use this hook considerably often this meant just one thing and one thing only — it’s time to wear my lab coverall and do some research, hopefully, busting some myths along the way! It’s rock’n’roll, baby!

Source: GIPHY

TLDR;

So the main problem with useEffect hook is that it executes provided callback only after all updates to the DOM are made. In other words, if your useEffect hook updates a state according to some condition, you will render the component, do all required updates to the DOM, still with the previous state, and only then you will trigger the re-render of the component with the next state.

On another hand, without useEffect, whenever React will detect an update of the state during the render, it will immediately toss the output of this render and will start a new render with the new state right away, without making any updates to the DOM, which are the most expensive part of the equation.

Updating state based on state

Verdict: CONFIRMED!

This is a pretty easy and highly unlikely one — searching through our codebase, I couldn’t find any actual case of such use. Any value derived from a state we would call a derived state and this value should not be stateful. Not hard to remember.

As the article suggests, you should calculate the derived state directly during the rendering or, if it involves some complex computation, use useMemo hook:

Updating state based on props

Updating the state based on provided props frankly speaking just sucks, or shall I say smells! If possible this should be avoided at all costs by lifting the state up. If you ever used git and had to resolve conflicts, try to think why on Earth I just mentioned this 🙃

Let’s consider three possible scenarios when props affect the state

State supersedes props

Verdict: CONFIRMED!

In a naive world, this would be as easy as this:

People who worked with real-life applications will immediately notice a problem — chances are defaultValue from the code above won’t be accessible synchronously during the constructor time, either because it comes from some kind of subscription, like React Context, or is an asynchronous stateful value by itself. In this case, we need something a bit smarter:

So why are we updating the state early, during the render, and not inside an effect? Because in this case, React will not make any changes to the DOM and will directly proceed with re-rendering the component, unlike when the update is initiated inside of the useEffect hook.

If you or people reviewing your code are freaked out a little by the code above, remember, that you can always abstract hook by custom hook:

Props supersede state aka Resetting the state

Verdict: CONFIRMED + BUSTED!

Disclaimer: It’s busted not because you have to use an effect, but because of the suggested in the article approach.

In our codebase, we reset the state based on props in many places, but all of the cases have the same pattern — we have some modal surface, like a dialog window or sidebar, that is always rendered and whose visibility is toggled by open prop. Sounds familiar? Sometimes we need to reset the internal state of this element whenever it’s open. The question is, how do you do that?

React team proposes changing a key of the component every time you need to reset a state and at first, it seemed like a genius idea:

This totally works, the state is re-instated every time dialogOpen changes, exactly what we wanted, right? Yeah, but at what cost?

First of all, we completely re-mount the whole subtree which defies the purpose the open prop was used for in the first place, secondly, we do it not only when the dialog is open, but also when it’s closed — we re-mount something that is not even visible on the screen. Not good!

Let’s see if we can improve this. Remember from the last example: the goal is to update the state early, during the render. We again will be using a stable reference to remember the previous state of the dialog window:

State and Props merge

Verdict: CONFIRMED!

By now, I’m sure you have a very solid understanding of how to avoid useEffect and update the state earlier by keeping some references to the previous values or state. Just for demonstration purposes, I’ll give you another approach to this scenario using the derived state method we discussed earlier, which works perfectly when you need to merge state and props together:

Words of caution

While playing with this and trying to remove useEffect hook from various places in our codebase I found a couple of scenarios where it’s either impossible or not that straightforward to remove it:

  1. You need to be extra cautious with functions aka callbacks passed as props to your component, as it’s very tempting to call those using the same principle — before any updates to the DOM are made. The problem arises when and if those callbacks will unmount your component because your component is only scheduled to appear in the DOM at this stage. So any such callbacks that will unmount the component have to be called inside the useEffect.
  2. Considering our example on top, using the stateInitialized reference, if your defaultValue will appear a little too fast you most likely will run into race conditions in React’s strict mode. The good news — it will work just fine in production, the bad news — it’s super trippy and very unpleasant.
  3. useEffect comes with a very convenient way to conditionally invoke the provided callback only when any of the values in the dependency array changes. On top of that, you have an exhaustive-deps ESLint rule guarding any potential fuckups. Without useEffect you’re on your own and the chances to create a stale state or blow up your stack by an infinite re-renders increase.

Conclusion

Aside from a little blunder with suggesting to update keys of the components to reset their state, I didn’t really bust anything — React team knows what they’re talking about, no surprises here. And yes! You and I and we’re all overusing useEffect and there is a very big chance you indeed might not need it!

--

--