Eslint react-hooks/exhaustive derps recursion - reactjs

I need some help wrapping my head around eslint react-hooks/exhaustive derps.
I ONLY want the effect to run when listenToMe has changed, but react-hooks/exhaustive derps is yelling at me to add history. This causes recursion. useEffect was React gold for me until this rule.
ESLint: React Hook useEffect has missing dependencies: 'history'. Either include them or remove the dependency array.(react-hooks/exhaustive-deps)
Can someone help me understand:
Why is it bad practice to only listen for changes you care about in useEffect?
What is the "right" way to only listen for specific changes on state change?
useEffect(() => {
if (listenToMe) {
const search = new URLSearchParams(location.search);
search.delete('q')
history.replace({ ...location, search: search.toString() });
}
}
}, [listenToMe]);
I've read through github and react, but I haven't read anything that clicks.
https://github.com/facebook/create-react-app/issues/6880
https://reactjs.org/docs/hooks-rules.html
Many others...

From the docs
It is only safe to omit a function from the dependency list if nothing in it (or the functions called by it) references props, state, or values derived from them.
The problem arises when you're using passed props or in your case, the history prop coming from a HOC or hook from react-router.
First of all, its passed as a prop, and props can inherently change.
Second of all, you're calling a history.push function. The linter doesn't know that push will always be the same function of the history class and that this function will not use any state or props itself.
The "right" way according to facebook is to move the function inside the effect, but that doesn't make sense if you're just reusing code from some file or using a function like history.push. So I think in your case the solution would be to wrap it in a useCallback with history in its own dependancy array. This is a last resort according to the devs.
Essentially, useCallback will simply return a memoized value instead of actually accessing the value and whenever the value in it's dependancy changes, there is a new callback with new memoized values.
History.push will of course always have the same identity so this somewhat of an anti-pattern.
Personally I have never had any problems passing in values like this. So I think writing a useCallback when you're just dealing with functions declared elsewhere (or any fully stable variable) is pointless and I find it reasonable to skip linting that line.
I keep the rule enabled as others have pointed out however, as its good to keep you on your toes about writing effects.

This eslint rule is only a hint useful in most situations, but in some situations you have to ignore it.
React.useEffect(()=> {
// I have some code with dependencies here but I only want to run it on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

React disabled the auto fixer
https://github.com/facebook/react/issues/15204
I installed the new devDependancies (eslint#latest and eslint-plugin-react-hooks)
npm install eslint#latest eslint-plugin-react-hooks#latest --save-dev
I tested in IntelliJ IDEA 2020.1 (EAP Build #IU-201.5985.32) and it shows the warning, but ESLint Fix does not auto add dependancies to useEffect
ESLint Warning in IntelliJ
ESLint: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array. If 'dispatch' changes too often, find the parent component that defines it and wrap that definition in useCallback.(react-hooks/exhaustive-deps)
VS Code has the "fix" in pre release 2.1.0-next.1, but I have not tested it.
https://github.com/microsoft/vscode-eslint/pull/814#issuecomment-587020489
https://github.com/microsoft/vscode-eslint/releases/tag/release%2F2.1.0-next.1
This does NOT answer the question, but helpfull to anyone who runs into react-hooks/exhaustive-deps auto "fix" issues.

Related

Resolving TypeScript React JS Lint: React Hook useEffect has a missing dependency without using comment to disable lint or without passing dependency

I am build a Web application using React JS and TypeScript. I am using React hooks. I created a custom hook and using that in a Component and calling a function of that hook inside that useEffect hook within the component as follow.
const Products: FC<{}> = () => {
const productsHook = useProducts();
useEffect(() => {
productsHook.fetchProducts();
}, []);
When I run lint, I am getting the following warning.
React Hook useEffect has a missing dependency: 'productsHook'. Either include it or remove the dependency array react-hooks/exhaustive-deps
When I add the following line,
// eslint-disable-next-line react-hooks/exhaustive-deps
right after this line, productsHook.fetchProducts();, it resolves the warning.
The other way to resolve the issue it to add productsHook.fetchProducts as an array element in the second parameter of useEffect like this.
useEffect(() => {
productsHook.fetchProducts();
}, [productsHook.fetchProducts]);
But, technically, it is unnecessary to do that. It will work perfectly fine if we don't do that too. Is there a way to resolve that warning without the comment to ignore or without passing the dependency in the second parameter of useEffect?
I would just do it the second way, adding the function to the dependency array. It is for instance what is recommended to do when using dispatch in Redux, even though the dispatch function is guaranteed to always be the same, eslint cannot detect that, and the easiest way to solve it is to add it to the dependency array.
It's definitely a better option than getting into the habit of disabling warnings that you don't like.

React won't let me use `useEffect` in a completely reasonable way

I created the following helper functions because functional components in React do not have mount and unmount events. I don't care what people say; useEffect is not a an equivalent. It can be as I demonstrate below:
//eslint-disable-next-line
export const useMount = callback => useEffect(callback, []);
//eslint-disable-next-line
export const useUnmount = callback => useEffect(() => callback, []);
React does not let me do this because I am technically calling useEffect from a non-component function. I'm doing this because when I use useEffect as a mount or unmount event, it pollutes my terminal with meaningless warnings about not including something in the dependency list. I know, I should be doing this...
export default function MusicPlayback(...) {
...
useEffect(() => stopMusic, []);
...
}
But then I get a warning about stopMusic not being included as a dependency. But I don't want it to be a dependency because then useEffect will no longer be an unmount event and stopMusic will be called on every render.
I know that it is eslint that is warning me and I can use //eslint-disable-next-line but that is too ugly to have in every single file that needs an unmount handler.
To my knowledge there is no way to have an unmount handler without using //eslint-disable-next-line absolutely everywhere. Is there some way around this?
Ok, the dependency check is there for a reason, even when you think it shouldn't be there.
useEffect(() => {
stopMusic()
...
}, [stopMusic, ...])
Let's talk about stopMusic, suppose this is a global function from another third party. If the instance never changes, then you should fire it as a dependency, since it won't hurt.
And if the stopMusic instance does change, then you need to ask yourself why you don't want to put it as a dependency, because it might be accidentally calling an old stopMusic.
Now, suppose you are good with all these and still don't want it to be wired with stopMusic, then consider use a ref.
const ref = useRef({ stopMusic })
useEffect(() => ref.current.stopMusic(), [ref])
Either way you get the point, it has to depend on something, maybe your business logic doesn't want to. But technically as long as you need to invoke something which isn't part of the useEffect, it needs to be a dependency. Otherwise from the useEffect perspective, it's an out-of-sync issue. The point of ref (or any object) is to get into this out-of-sync deliberately.
Of course, if you really hate this linter rule, i believe you can disable it.
NOTE
React community is proposing a way in the future to add these dependencies for you behind your back. The rational behind it is that React is designed to be reactive to the data in one-way train.
This is what I ended up having to do to stop the music on unmount.
export default function MusicPlayback(...) {
const [playMusic, stopMusic] = useMagicalSoundHookThingy(myMusic);
const stopMusicRef = useRef(stopMusic);
stopMusicRef.current = stopMusic; // Gotta do this because stopMusic no longer works after render.
...
useEffect(() => {
const stopMusicInHere = stopMusicRef.current; // Doing this to avoid a warning telling me that when called the ref will probably no longer be around.
return stopMusicInHere;
}, [stopMusicRef]);
...
}
Using a ref like this isn't meaningful. It is just a clever hack to fool eslint. We are packaging something that changes on every render into something that doesn't. That's all we are doing.
The problem I am having is that the entity I'm interacting with is static. But the means to communicate with that entity (namely the function stopMusic) is transient. So the brute force means by which useEffect determines dependence isn't nuanced enough to really indicate whether some dependency has actually changed. Just the tiddlywinks that invoke that dependency, the functions and object created by the hooks. Perhaps this is the fault of the hook writer. Maybe the tiddlywinks should maintain the same life cycle as the entity.
I love React very much, but this is an annoyance I've had for a while, and I'm tired of people telling me that I should just include all the dependencies eslint demands as if I don't really understand what dependencies are actually involved. It is probably ideal to never have any side effects at all in a React program, and rely on a data repository pipeline like Redux to provide any context. But this is the real world and there will always be entities with disconnected lifecycles. Music playing in the background is one such entity. Such is life.

React, ESLint: eslint-plugin-react-hooks shows incorrect "missing dependency"

Assume you are using React and you are writing a custom hook useSomething that returns the identical same thing each time it is invoked for the same component.
const something = useSomething()
// useSomething() at time X === useSomething() at time Y
If you now use this something value inside of a useEffect(() => ...) and you do not pass something as a dependency to the array of the second argument of useEffect then the linter will warn you:
React Hook useEffect has a missing dependency: 'something'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)
Of course ESLint cannot know that something will always stay identical (per component), but adding not-changing things like something to the dependency array of useEffect each time they are used is really annoying. Just deactivating react-hooks/exhaustive-deps does also not seem to be a good solution (nor using // eslint-disable-next-line react-hooks/exhaustive-deps).
Is there a better solution than to add things like that unnecessarily to the dependency array of useEffect just to make the Linter happy?
Please find a simple demo here:
https://codesandbox.io/s/sad-kowalevski-yfxcn [Edit: Please be aware that the problem is about the general pattern described above and not about this stupid little demo - the purpose of this demo is just to show the ESLint warning, nothing else]
[Edit] Please find an additional demo here:
https://codesandbox.io/s/vibrant-tree-0cyn1
Here
https://github.com/facebook/react/issues/14920#issuecomment-471070149
for example you can read this:
If it truly is constant then specifying it in deps doesn't hurt. Such as the case where a setState function inside a custom Hook gets returned to your component, and then you call it from an effect. The lint rule isn't smart enough to understand indirection like this. But on the other hand, anyone can wrap that callback later before returning, and possibly reference another prop or state inside it. Then it won’t be constant! And if you fail to handle those changes, you’ll have nasty stale prop/state bugs. So specifying it is a better default.
So maybe just adding that never-changing values to the dependency array of useEffect may yet be the best solution. Nevertheless I hoped there would be something like a ESLint react-hooks configuration possibility to define a list of hook names which whose return values should be considered as static.
The example is a little contrived but I suspect you may wish to create a new useEffect block without this dependency.
If the store is not changing though I'd question why you'd wish to console log it time. If you wish to log it only on change then you'd add someStore to your dependency array. It really depends on what you're trying to achieve and your seperation of concerns.
I'd argue that if someStore is used as part of whatever logic is handled in this effect then it does belong in your dependency array.
You could also alternatively move const something = useSomething() into your effect and extract it as a custom hook link
useEffect(() => {
console.log("Current state (may change)", someState);
}, [someState]);
useEffect(() => {
console.log("Current store (will never change)", someStore);
});

Correct/Incorrect to ignore some React useEffect dependency warnings?

Here's some sample code I've written that works fine:
useEffect(() => {
if (!rolesIsLoading && rolesStatus === 200) {
customer.roles = rolesData.roles;
}
}, [rolesIsLoading, rolesStatus]);
I'm getting this warning in the console:
React Hook useEffect has missing dependencies: 'customer.roles' and 'rolesData.roles'. Either include them or remove the dependency array react-hooks/exhaustive-deps
The thing is, the code works fine now and, in some cases, when I add such dependencies, as instructed, I end up with an endless loop or some other problem.
I'd appreciate any advice you might have as to how you resolved this when you've encountered a similar situation.
From React 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.
However, this may be overkill in some cases, like the subscription example from the previous section. We don’t need to create a new subscription on every update, only if the source props has changed.
To implement this, pass a second argument to useEffect that is the array of values that the effect depends on. Our updated example now looks like this:
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);
So, from the exercpt above we see that the dependencies array serves the purpose to conditionally fire an effect.
That warning you're getting is because you're using some external (from the effect's perspective) variables in your effect function that you're not mentioning in the dependency array.
React is worried that you might be getting new values for those variables in future renders, and since your effect uses them, React "default intent" is to re-run that effect with the new variables values. So your effect would always be up to date.
So you need to think if you want to re-run your effect if/when those variables change in the future.
Basic recommendations:
Add them to the dependency array
If they never change, there will be no difference
If they change, add some conditionals to your effect to decide whether you should do something based on the new variables values or not
If they're changing only in reference, but not in value (like functions, arrays, objects, etc), you can use the useCallback or useRef hook to preserve the "reference" for those variables from the first render and not get new references on every render.

React hooks: how to access props within "mount" useEffect & not throw linting warning

Unless I'm mistaken, this is valid code:
useEffect(() => {
if (prop1) {
doSomething();
}
}, []);
(prop1 is a prop). But when linting I get the following error:
React Hook useEffect has a missing dependency: 'prop1'. Either include it or remove the dependency array.
(react-hooks/exhaustive-deps)
I don't want to pass prop1 as a dependency because I would lose the "only run on mount" behaviour. But I need to access the prop to doSomething().
Any suggestions?
Hooks were new when this question was written, so maybe you already know this, but in case you or someone else wants to know:
React thinks that because your effect uses the value of prop1, it "depends" on prop1 and should be re-run whenever it changes. That's why the linter is complaining that it's not listed as a dependency.
However because you want the effect to only run "on mount", you want it to use the value of prop1 from the initial/first render, and never run again even if prop1 changes. This is at odds with the conceptual idea that the array lists all the variables the effect depends on, which is what the linter is focused on.
The solution alluded to in the React Hooks FAQ is to use useRef to keep track of whether or not this is the first render (edited):
const firstRenderRef = useRef(true)
useEffect(() => {
if (firstRenderRef.current) {
firstRenderRef.current = false
doSomething(prop1)
}
}, [prop1])
This solution satisfies the linter, specifically because it follows the idea of listing all dependencies of the effect in the array. The ref allows the effect to also depend on a variable for whether this is the first render or not, but without rerendering when the value changes.
You could probably raise this here.. [ESLint] Feedback for 'exhaustive-deps' lint rule
Though I get the feeling where this is a case where you should add an eslint "ignore" comment if you are sure you don't want the effect to run on update of prop1.
A legit case for relaxing the warning was raised here..
https://github.com/facebook/react/issues/14920#issuecomment-467896512
Also check what version of the plugin you are running..
https://github.com/facebook/react/issues/14920#issuecomment-468801699
try this code
const usemount = ( functionToDoSomeThing, data ) => {
useEffect( () => {
functionToDoSomeThing( data );
},[] );
};
usemount( console.log, props );
i define a function to do Something and pass it to hook
in example i use console.log function

Resources