Reputation: 1271
I can turn that rule of for entire array:
useEffect(() => {
if (id) {
stableCallback(id);
dynamicCallback(id);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, dynamicCallback]);
But my preference would be to achieve something along these lines (pseudocode):
useEffect(() => {
if (id) {
stableCallback(id);
dynamicCallback(id);
}
// eslint-disable-next-line react-hooks/exhaustive-deps stableCallback
}, [id, dynamicCallback]);
In my imagination, the usage of "stableCallback" does not trigger the warning, but if a new dependency emerges within it, I will see the warning about it (I know that if stableCallback
is not changed then it should not matter - but that's only an example).
Is there a rule similar to exhaustive-deps, or any alternative approach that would allow me to utilize it in a similar manner?
I haven't found any alternative to it.
Upvotes: 4
Views: 1710
Reputation: 72865
I've spent quite a bit of time on this since I assigned the bounty to it, and I think that building semantically well-named custom hooks may be a great way to handle this. Instead of using a useEffect
in place, you can create a custom hook that takes the specific dependency as a prop:
const useIdChanged = (onChange: (...args: unknown[]) => void, id: string) => {
// very important that you call, not return the useEffect here
useEffect(() => {
onChange();
// eslint-disable-next-line react-hooks/exhaustive-deps
// or if you memoize the `onChange` function in the parent,
// you can safely pass it as a dependency here, too
}, [id]);
});
// ...
const SomeComponent = () => {
useIdChanged(() => {
if (id) {
stableCallback(id);
dynamicCallback(id);
}
}, id);
return <></>
};
Despite the fact that you still have to use the eslint-disable-next-line
flag, because you gave the hook a specific meaningful name, and the observable(s) are explicitly passed as named props, it's got some robust protection against "I don't know what this hook is doing, therefore, I will change it".
Upvotes: 1
Reputation: 11
Not a clean solution but it will let you mark the variable as intentionally ignored and perhaps could be easily implemented by 'react-hooks/exhaustive-deps' contributors.
Pass the dependency you intentionally ignore with a void operator:
useEffect(() => {
if (id) {
stableCallback(id);
dynamicCallback(forgottenId);
}
}, [
void stableCallback,
id,
dynamicCallback
]);
This way, the dependency is listed in dependencies and won't cause useEffect
to retrigger on change.
However 'react-hooks/exhaustive-deps' will complain about complex dependency anyways and won't see its literal, but it should be easily implemented in:
https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js (as else if
near if (declaredDependencyNode.type === 'Literal') {
).
Upvotes: 1
Reputation: 16189
You cannot do this and there is no rule for excluding a specific dependency. In fact, you don't have to follow Eslint, especially when it comes to including dependency, sometimes it suggests including values that you don't really want to include.
You should think of dependencies as data that once change value between renders, the corresponding hook is triggered.
If one dependency is immutable (string, number, boolean) then there is no problem but when it is mutable (object, array) then you have to memorize it using useMemo to avoid triggering the hook on every component render.
Now when you include a callback function as a dependency, this will make the hook run each time this function is recreated, I don't think you want to achieve that. maybe you just need to keep the callback function recreated each time it needs to, and trigger useEffect
to run only when id
is changed:
const dynamicCallback = useCallback((id) => {
console.log(id + x + y);
},[x,y])
//...
useEffect(() => {
if (id) {
stableCallback(id);
dynamicCallback(id);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id]);
This way, the most recent version of dynamicCallback
will run each time id
changes
If you really want to trigger useEffect
every time dynamicCallback
is recreated, maybe you could make it a simple function inside useEffect
and include the necessary dependencies right there, so whenever the effect is triggered, the function is recreated and then called
useEffect(() => {
const dynamicCallback = () => {
console.log(id + x + y)
}
dynamicCallback();
}, [id, x, y]);
Upvotes: 4