useEffect exhaustive-deps warning: what possible issues is this trying to avoid? - reactjs

I realize there have been many questions about React's useEffect hook's dependency array, and the eslint warning that may arise from missing dependencies. Some other good discussions about this:
How to fix missing dependency warning when using useEffect React Hook
React useEffect Missing Dependency
React useEffect missing dependecy?
React Hook useEffect has a missing dependency with useEffect
Although the causes & solutions for these warnings are very clear, one thing I'm struggling to grasp is the why. First, to quickly summarize, my understanding is that useEffect as three general purposes:
If its dependency array is omitted, it acts like both componentDidMount and componentDidUpdate: it runs the first time the component is rendered, and all subsequent renders.
If its dependency array is empty, it acts like componentDidMount: it can be used to run first-time initialization code (e.g. fetching initial data from the server, etc).
Otherwise, you can include in its dependency array any variables that you want it to 'watch'; it will act like componentDidMount, and then like componentDidUpdate when those variables change.
(For completeness: its return value can also be used to implement componentWillUnmount behavior, but that's beyond the scope of this issue).
I understand that eslint will complain if useEffect references any functions (or other variables) which are declared outside of useEffect. Per the above links, there are various ways to resolve these warnings: Move the function definition inside useEffect (if nothing outside useEffect needs to call it); add the function to the dependency array & memoize it with useCallback; disable the warning with // eslint-disable-next-line or // eslint-disable-line react-hooks/exhaustive-deps; etc. These all make sense. What I'm struggling to understand is WHY it even complains about this.
So, my specific questions:
Practically speaking, I can't understand why you would ever want a reference to a local function in the dependency array. For example, if you have useEffect(() => { checkCurrentUser() }, []), and checkCurrentUser needs to be defined outside useEffect because it's called elsewhere, checkCurrentUser will be re-defined on every component render (which is why if we put it in the array, we should memoize it w/ useCallback). But to me, it makes no logical sense why one would ever want this in the dependency array at all. So why is this even a warning? What is this warning actually helping to prevent? Throughout all the times I've seen the warning, in every case, the application has behaved exactly as I expected/intended.
If one of useEffect's primary purposes is to 'run once' like componentDidMount, and the way to achieve that behavior is to explicitly put an empty array, why does it hassle you if you specify an empty array? I can understand why it might make sense to warn if you have a dependency array that contains some references but not others - as in 'oops, you forgot some' - but the empty array is a specific, well-defined usage. To me, it doesn't seem to make sense that it would tell you 'an empty array is an issue,' as that's the only way to achieve componentDidMount-type initialization. It feels like the same thing as just warning you "don't use componentDidMount()." Why does it complain about an empty array, if that's supposedly one of the 3 main uses of useEffect?
Again, I do understand the circumstances in which the warning appears, and the ways to resolve it. I just struggle to understand the benefit of even having it, (1) in the case of functions (which we definitively know will change on every single render), and (2) in the case of an empty array (which are a specific usage).

(1) in the case of functions (which we definitively know will change
on every single render)
The reason for requiring to put functions in dependencies
seems to be the same as the reason why they require you to put any other values in dependencies:
what can happen is that a function you are using might be itself referencing some values from component scope, which can become stale.
(2) in the case of an empty array (which are a specific usage).
In this case I will reference answer cited by Dan Abramov from this comment:
I think the biggest gotcha with class life cycle methods like
componentDidMount is that we tend to think of it as an isolated
method, but in fact it's part of a flow. If you reference something in
componentDidMount you will most probably need to handle it in
componentDidUpdate as well, or your component may get buggy. This is
what the rule is trying to fix, you need to handle values over time....
You may find more info on why they did it that way in that thread.

Related

Why is it bad practice to not fill the dependency array with all dependencies?

I've been looking around without finding any concrete answer, so I thought I'd ask.
React's useEffect famously has a dependency array, which tells React when to re-run its function.
If it's missing, the function is run every re-render.
If it's empty, the function runs just once.
If it has dependencies, it re-runs every time one of the dependencies change.
Why is it then that ESLint complains about missing dependencies, if I don't want it to trigger a re-run when most of them change?
Suppose I have a scenario like this:
useEffect(() => {
api.send(someUrl, somePayload);
}, [changeMe]);
This would be incorrect according to ESLint, as neither api, someUrl, or somePayload are tracked among the dependencies, but I don't want it to trigger every time any of them change, just when changeMe changes.
On more than one occasion I've had a useEffect loop trigger because it insisted on tracking a function that really didn't need to be tracked to achieve the desired functionality. I'd even wrapped the function in useCallback to avoid the reference changing every render, and it still caused an ungodly amount of re-runs.
I know I can disable the ESLint for that line, but the linter is complaining for a reason, I'm just not sure the reason is always warranted.
So what kind of subtle bugs do I risk running into by not filling the dependency array?
What is the real danger?

React Why is it considered a performance optimisation to include functions in the list of dependencies?

According to the docs we don't want to omit functions from the dependencies of a useEffect(). Why is this? What are the performance implications?
If you omit functions and your component grows, there might be a chance that at some point in time your function may need some props and indirectly your effects may lie about dependencies.
That's why the recommendation is to hoist functions that don’t need props or state outside of your component and pull the ones that are used only by an effect inside of that effect.
If useEffect still needs function as dependencies then wrap them in useCallback.
This way we no longer have to think about the transitive dependencies. Our dependencies array isn’t lying anymore: we truly aren’t using anything from the outer scope of the component in our effect. And your code is performant and optimal.
refer more here: moving-functions-inside-effects

Is it always necessary to add all used functions within useEffect to its dependencies array?

I am currently building a Tetris game on React just to practice hooks (used to develop in class components back in the day, kind of left React for a while and yesterday I decided to use it once again).
The game is working perfectly well, and it behaves as expected on each and every situation, however, there is a constant warning related to using a function within useEffect without it being a dependency.
To clarify - I have a useEffect function that all it does is call an updateFunction and is dependent just on the x and y coordinates of the moving Tetris block. The update function updates the state of the board whenever the position of the shape changes.
I know that React re-creates functions on each and every render, but giving a useCallback to the update function would cause it to be re-created endlessly (as then, the complier would ask me to make it dependent on the board state, thus each time it updates the board, it'll be forced to be re-created once again), and this causes an infinite loop of rendering.
Is it really necessary to put every function within useEffect as a dependency, even if said functions only causes a visual rendering to show the current state of the game?
You can always put whole function inside useEffect and you want get this error and problem
Someone just asked the similar question, and my answer is there, React won't let me use `useEffect` in a completely reasonable way
The answer to your question is that, no you don't have to, if the warning doesn't bother you, and your code is still working, then move on.
Otherwise you can try to find a way to disable this linter, or ask yourself why not to meet all the dependency requirement.
It's best to think of the effect dependencies as "correct" or not, and not try to tailor them to achieve some specific behavior. (The vast majority of the time anyway)
This means that if an effect uses a value that could possibly change, then it's declared as a dependency. Sometimes you app works fine if your dependencies are "incorrect" because elsewhere you can guarantee a value is constant. But this is more about maintaining the application than having it work currently.
Later if you change how one of the dependencies works in a refactoring, then the effect now may need to re-run and doesn't leading to strange and hard to diagnose bugs like stale values where you can't tell where they come from.
If this leads to cumbersome hooks with huge dependency arrays that infinite loop themselves, then it's an indication that logic is messy and needs to be refactored, split into multiple effects, rethink how the data flows through your hooks, etc.
It's hard to advise specifically without seeing your code. But in your case it seems like you could make the "update function" take arguments instead, which means it could then be completely static. Or if you did use useCallback that depends on x,y then it would regenerate when those change, but you probably have a lot of logic to run when those change, so that's probably expected.
Summary:
I would advise you to take the warnings of eslint-plugin-react-hooks seriously, and clean your code until it passes. In the long wrong, in a large and complex application, it really helps keep things clean.
There are very rare exceptions that come up when doing non standard things, but it's worth it to try your best on this before resorting to ignoring those warnings.

useContext with immutableJS

I have an Object of data that I store as my context. It gets filled with API responses and I feel like performance-wise I'm doing something wrong.
I tried to look into ImmutableJS but there is no reference of combining ImmutableJS with ContextAPI. Is combining them together doesn't give any benefits? And if it does do have an example of to how develop such a thing?
Whether you use context directly, add redux or pass down props through your component makes no difference to how you use immutable. ImmutableJS simply makes sure that an object does not change unintentionally and that it can be checked for changes with fast reference equality.
The problem you describe could arise from the fact that your context object changes whenever any response data is modified. Therefore it would trigger a rerender on all context consumers (see caveats). I like Immutable and use it in a large project, but if I am correct, it won't make a difference, you would have to fix the root cause or handle changes with shouldComponentUpdate.
I suggest you look into redux (or some other context provider) or show us code.
Don’t try solving your problem by introducing a dependency. It won’t be a magic bullet.
From what you describe, your context object changes frequently and is causing your component tree to rerender too often.
Maybe try creating multiple contexts that are only relevant for parts of the app. That way rerenders will only happen with subtrees using specific context.

Should I rely on the dependency list check of react useEffect hook?

Consider the following code
useEffect(effect, [v]) // v can be undefined
I want to run the effect function only after the first render or when v changes.
My questions are
Do I need to check whether v changes in effect? Or I can rely on react to check the value of the dependency list (i.e., the [v]).
If the answer of 1. is the latter, is this behavior supposed to be changed in the near future? For e.g., when the concurrent mode is released.
From react official docs
Conditionally firing an effect
The default behavior for effects is to
fire the effect after every completed render. That way an effect is
always recreated if one of its dependencies changes.
It says if one of its dependencies changes, then, an effect is recreated. However, it does not mention the reverse: an effect is recreated if and only if one of its dependencies changes.
I only remember that in the early stage of hook, I read somewhere that the effect function can be run in some cases even without v's change, in order to optimize the memory, or in concurrent mode?? I really do not remember exactly and could not find out the source.
It would be very helpful if anyone can tell me how exactly React does, or refer to the related internal source code of react. I think they would have a special check of the first render, otherwise, when v is undefined after the first render, effect will not run.
I also posted the question on react repo here. The thread helped me solved my question.
Hope that it also helps others.

Resources