Misconceptions about the React's useEffect hook

I started lately adopting the React Hook approach for some of my side projects. I have got my self acquainted with most of the theoretical stuff around hooks and what they could bring to the table in terms of reusability, abstraction and performance.

What really caught my interest is the useEffect hook. I couldn't really get my head around it, especially that I am coming from calss-based component pattern. I think an answer to my question below will help me have a good grasp of how this hook is designed under the hood and how it's intended to work. So please any answer/advice is appreciated.

Here we go:

Topic: 1 useEffect VS multiple useEffects:

  1. Besides separation of concerns what's the added value of using multiple useEffects?
  2. Couldn't we just use one useEffect hook all over the component and within it we check if our variable has changed or not? This way we will eliminate the overhead of using another custom hook i.e: useeffect with deep comparison since now our comparison is happening inside the hook itself and we could leverage deep comparison with defining an other hook.

Basically we will be entering useEffect with every re-render and our block of IFs will take care of either initiating the appropriate side effect or not, meaning no loss of performance here since we are not going to execute not needed code.

What I am referring to could be designed this way:

   useEffect(() => {
        if (!_.isEqual(previousStateObject1, stateObject1)){
            // Do something
        }
        if (!_.isEqual(previousStateObject3, stateObject2)){
            // Do something
        }
        if (!_.isEqual(previousStateObject4, stateObject3)){
            // Do something
        }
        if (!_.isEqual(previousStateObject4, stateObject4)){
            // Do something
        }
        ...
   });
//Note: Let's assume that we always have reference to any previous object i.e: previousStateObject1, previousStateObject2 ...

Is there anything wrong with this approach? What could be the alternative and how to deal with the deep comparison issue?

I believe we can ask the question differently:

What's the tradeoff of using a bunch of IFs to control when an action within useEffect should be triggered or not versus relaying on the dependancy array to tell when the hook should be triggered or not?


Besides separation of concerns what's the added value of using multiple useEffects?

Don't discount the value of separating concerns to aid maintainability of code.

That said, if you have multiple useEffect hooks then they can have different sets of dependencies, so you don't run code that doesn't need to run when one of the dependencies changes.

Couldn't we just use one useEffect hook all over the component and within it we check if our variable has changed or not?

Basically we will be entering useEffect with every re-render and our block of IFs will take care of either initiating the appropriate side effect or not, meaning no loss of performance here since we are not going to execute not needed code.

Except you are. Now you have two checks for the same thing.

The hook is checking to see if any of the dependencies have changed, and then you have your own checks to see which of them have changed.

What could be the alternative and how to deal with the deep comparison issue?

To not set a state object if you're setting it to be a clone of the previous value.


It would be theoretically possible, but verbose and confusing, because of this line:

if (!_.isEqual(previousStateObject1, stateObject1)){

Having and using the current state is natural and easy.

Having and using the previous state is not - you'll need a ref and another effect hook.

what's the added value of using multiple useEffects?

It's easier to make sense of, I think. When I see [someVar] I know "So this runs exactly when someVar changes, and nothing else." In comparison, if one sees [stateObject1, stateObject2, stateObject3, stateObject4] in the dependency array, it'll probably be a lot more difficult to determine at a glance what the script-writer intended for that effect to do.

meaning no loss of performance here since we are not going to execute not needed code.

98% of the time, performance is not an issue worth worrying about for a given section of code. If you find that something isn't running as fast as desired, run a performance test and check what's taking the most resources, and fix that area. Trying to optimize for performance everywhere regardless of that section's effect on the application as a whole will take a whole lot more time, and will result in code that's more difficult to understand; it's often not worth it.