Home > Mobile >  In React, what is the correct approach to preventing a useEffect hook from executing more than once
In React, what is the correct approach to preventing a useEffect hook from executing more than once

Time:08-07

I have found it to be extraordinarily difficult to control the useEffect hook.

This experience has been incredibly debilitating and counter productive. I am losing hours upon hours having to troubleshoot the useEffect hook which has me second guessing using the React framework all together at this point for Angular instead. Angular components have life cycle hooks that are incredibly clear, practical and simple to control.

Time and time again, i am running into scenarios where an instance of my useEffect hook executes more than once despite NO CHANGE to its dependencies.

To prevent this behavior, I am finding myself constantly having to go up the tree to determine the cause of a component re-render which i suspect is what triggers these unwanted and subsequent executions of my useEffect hooks, but what do we as developers do if there is no way to avoid a component re-render and we need to prevent a useEffect hook from executing again as a result?

In searching SO, I have seen other developers with similar problems resorting to attempts at replicating the behavior of the former componentDidMount life cycle method by using the useEffect hook and excluding its dependencies all together as a solution like so:

useEffect(() => {
  if(dependencyOne !== 'value') {
    // do some work
  }

}, []); // <-- If i am understanding the useEffect documentation this approach is not recommended.

It is my understanding however, that when implementing useEffect, if a dependency is referenced inside the hook, it SHOULD ALWAYS be included as a dependency in the closing array like so:

useEffect(() => {
  if(dependencyOne === 'value') {
    // do some work
  }

}, [dependencyOne]); 

And that does make sense. useEffect will, by design, run again whenever a dependency changes, as intended.

However, TO BE CLEAR, i am running into issues where my useEffect hooks are executing more than once despite the fact that their dependencies have NOT CHANGED.

Thus, the question, if we cannot prevent a component that contains a useEffect hook from a re-render, what is the best practice approach to prevent that useEffect hook from executing more than once when it is known that the useEffect has dependencies that have NOT changed?

I have been doing the following which to me just does not feel right nor practical in situations which are much more complex that reference multiple props or local states:

First, outside of my component, i define a boolean:

...
let initalChecksComplete = false; // <-- boolean used to control useEffect execution
const MyComponent: React.FC<ContainerProps> = (props) => {
...

And then inside my useEffect hook i do this:

useEffect(() => {
  if(!initalChecksComplete && dependencyThatDoesNotChange === 'value') {
    initalChecksComplete = true;
    // do some work
  }
  console.log('Confirmation that this hook executes more than once.');
}, [dependencyThatDoesNotChange]); 

Seems to me that it should not be necessary to use booleans in this way to prevent subsequent execution despite knowing factually that none of the dependencies have changed, and i suspect it goes against best practices in a React application.

Looking at the React documentation, it seems that React sort of suggests that it is ok to note taken from React site

NOTE: It may also be the case that i am running into multiple useEffect execution issues due to the following issue, still open at the time of this writing, on github surrounding Restrict Mode which may be causing my hooks to execute unexpectedly: github issue can anyone confirm if this could be true?

CodePudding user response:

As mentioned in the docs, strict mode in react 18 got stricter, and therefore will mount and unmount your component in dev mode only. React will not re-run useEffect unless dependencies have changed. To debug this, here's what I suggest:

  1. Disable strict mode and try to reproduce the issue, see if it's just strict mode
  2. If that doesn't do it, verify that your dependencies haven't changed due to a parent re-render
  3. Finally, check the type of your dependencies, people have run into situations with with object types, that when a new object is created, useEffect would re-run

That's how I'd go about it.

CodePudding user response:

If you add a dependency to useEffect's array, it won't run until that dependency changes. So maybe the dependency is state variable that got updated or your dependency is a reference type which means that it will be recreated again (in this case wrap it with useCallback hook) or maybe a parent component re-renders which causes this component to re-render again.

  • Related