Function `useCallback` should have a dependency argument (like in React and Preact)
mcjazzyfunky opened this issue · comments
In difference to React and Preact in Dyo useCallback
does not have a dependency argument.
The Dyo documentation states:
useCallback
[...]
Unlike useMemo the returned function is guarenteed to always be a "stable" function reference.
This sounds like a good feature first, but actually it isn't - as it violates one of the core principles of the whole hooks idea that the closures within the render functions always have access to the relevant values at the time the render function was called.
It's a bit difficult to explain what I mean, hopefully the following demo will help a bit:
https://codesandbox.io/s/stoic-shockley-ubo50
Of course you can just use useMemo
instead for such cases - but why should you? Shouldn't callbacks not just always work with useCallback
?
PS: This is the implementation of useCallback
in Preact:
/**
* @param {() => void} callback
* @param {any[]} args
*/
export function useCallback(callback, args) {
return useMemo(() => callback, args);
}
You make a fair point. We could delegate the current behaviour to only bare useCallback
that do not provide dependencies like useCallback(() => {})
, the same can be said for bare useMemo(() => {})
when dependencies are omitted given the no-op nature of that variant.
I strongly believe the best solution would be (like in Preact and React):
useCallback(callback) // return value will always change
useCallback(callback, []) // return value will never change
useCallback(callback, [...someValues]) // return value will change when dependencies change
Same for useMemo
(as already is):
useMemo(computation) // return value will always change
useMemo(computation, []) // return value will never change
useMemo(computation, [...someValues]) // return value will change when dependencies change
So the second argument would have the same semantical meaning in useCallback
, useMemo
, useEffect
etc
Why not
useCallback(callback) == useCallback(callback, []) // return value will never change
useMemo(computation) == useMemo(computation, []) // return value will never change
Having the value always change negates the hook entirely.
I think, that would be a bit inconsistent regarding the semantic of leaving out the second argument as it would be completely different to the semantic of leaving out the second argument for useEffect
, wouldn't it?
PS: [Edit] Okay, I see: Why should you ever want that "will always change" case with useCallback
?
PPS: [Edit] ESLint shows the following warning if you do not pass a second argument to useCallback
(similar for useMemo
):
React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies? (react-hooks/exhaustive-deps)eslint
Maybe an argument for useCallback(callback, [])
and useMemo(calculation, [])
would be at least that with this syntax and semantic the react-hooks ESLint plugin would also work for Dyo....
Exactly, both useMemo
and useCallback
should never be used without a second argument.
I think removing extraneous , []
everywhere has value.
I'm getting this warning, React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?
, but I DID pass a second argument. The dependencies array, empty, or with dependencies. Sill get the warning. It's driving me crazy
Dyo doesn't emit React warnings, probably an issue with a lint tool you are using.
I'm getting this warning,
React Hook useCallback does nothing when called with only one argument. Did you forget to pass an array of dependencies?
, but I DID pass a second argument. The dependencies array, empty, or with dependencies. Sill get the warning. It's driving me crazy
Pass in an empty array to fix the warning ->
const allowButtonHandler = useCallback(() => { setAllowToggle(true) }, [])