Need to wrap every useMemo dependency in a useCallback - reactjs

I hope someone can help me. I tried to find the answer to my question but I couldn't, so if it is there and I couldn't find it, I apologize in advance.
So, I have an expensive operation that depends on 3 objects stored in the redux store. Since it is expensive, I want to execute it only when any of those 3 objects change.
To avoid making the function executed by useMemo too complex I split it in smaller functions that are then call when needed, something like this:
const computedValue = useMemo(() => {
...
const result = processStoreObject1(storeObject1)
...
}, [storeObject1, storeObject2, storeObject3, processStoreObject1])
Now, I do not want to list processStoreObject1 as a dependency of useMemo, the computed value does not depend on it, the computed value only depend on the 3 store object. However, if I do not list the function as a dependency of useMemo I get this lint warning:
"React Hook useMemo has a missing dependency: 'processStoreObject1'. Either include it or remove the dependency array. eslint(react-hooks/exhaustive-deps)"
Because of this warning I have to include the function in the dependencies array, and because the function is declared inside the component, similar to this:
const MyComponent = () => {
...
const processStoreObject1 = () => {
// Do something
}
...
}
I have to wrap it in a useCallback, otherwise it changes with each render and the useMemo is recalculated all the time (which is wrong). The warning that I get if I do not wrap the processStoreObject1 with useCallback is this:
"The 'processStoreObject1' function makes the dependencies of useMemo Hook (at line NNN) change on every render. To fix this, wrap the 'processStoreObject1' definition into its own useCallback() Hook.eslint(react-hooks/exhaustive-deps)"
I know one easy solution is to define processStoreObject1 outside the component so it does not get created in each render but I do not like that way, the function is only consumed inside the component so I want to have the definition in there.
To summarize, the question is, how can I use a function inside the function executed by useMemo without adding the dependency to the dependencies array. I know it is doable, I saw some examples of functions being used without being in the dependencies array.
I will be thankful if anyone can help me.
Thanks !

Related

Function rendering multiple times. UseCallback React

I am trying useCallback for first time. I am not able to see any changes from earlier output in terms of function re rendering.
My function structure will be
const myfunction = ( array1, array2) => {
//doing a very big calcuation
}
what I tried to use is
const getCards = useCallback((a,b) => myfunction(a,b),[myfunction])
I tried to to pass a,b in dependencies of Usecallback which gives me parameters not found error.
I am passing getCards into the component like this
<Component summaryCallback={getCards}>
I am trying to reduce the number of call inside my function. I want to call this function only if there is a change in array1 or array2 from my previous rendering
Thanks in advance
All hooks are stored with their respective dependencies. If you use a variable that's not included as a dependency to a hook, their initial value is used.
Adding it to the dependency list will enforce the update of the hook and therefore all data inside the hook.
Either way, you might not at all use useCallback if you've got large/heavy computations. You might consider useMemo for it. Ref: https://reactjs.org/docs/hooks-reference.html#usememo
You might use it like the following:
const cards = useMemo(() => { /* heavy computation */ }, [depA, depB]);
return <CardList cards={cards} />
It rerenders everythime, you're dependencies depA or depB change.

Eslint react-hooks/exhaustive derps recursion

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.

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 appends unwanted value with eslint-plugin-react-hooks#next

I have a useEffect hook in my component which call a Redux action to fetch some data.
useEffect(
() => {
props.getClientSummaryAction();
},[]
);
When I go to save the file the linter does this.
useEffect(
() => {
props.getClientSummaryAction();
}, [props]
);
Which obviously send my component into an infinite loop as getClientSummaryAction fetches some data which updates the props.
I have used deconstruction like this and the linter updates the array.
const { getClientSummaryAction } = props;
useEffect(
() => {
getClientSummaryAction();
},
[getClientSummaryAction]
);
Which I am fine with but it doesn't really make sense because getClientSummaryAction will obviously never be updated, its a function.
I just want to know why the linter is doing this and whether this is best practice.
It's not unwanted. You know for a fact that the dependency is not going to change, but React can possibly know that. The rule is pretty clear:
Either pass an array containing all dependencies or don't pass anything to the second argument and let React keep track of changes.
Pass the second argument to useEffect is to tell React that you are in charge of hook's call. So when you don't pass a dependency that is included inside your effect eslint will see this as a possible memory leak and will warn you. DO NOT disable the rule just continue to pass the dependency as you are already doing. May feel redundant but it's for the best.

Resources