react hooks: how to handle co-dependent useCallbacks
useCallback
is essentially a version of useMemo
, specialized for memoizing functions. If two functions are co-dependent and can't be memoized separately with useCallback
, they can be memoized together with useMemo
:
const { onMouseMove, onMouseUp } = useMemo(() => { const onMouseMove = (e) => { if (!isSwipe(e)) { onMouseUp(e) } }; const onMouseUp = (e) => { document.removeEventListener('mousemove', onMouseMove) } return { onMouseMove, onMouseUp };}, [/* shared deps*/]);
This is the general pattern I'd use to memoize functions together, and I think it's the simplest answer to the general question. But I'm not sure, looking at the actual code here, that it's the best approach - the removeEventListener
may not work if the onMouseMove
is a different function than the one that was registered due to useMemo
recalculating. Might be more of a useEffect
sort of use-case, in practice.
after some research, looks like the solution for (useCallback() invalidates too often in practice) also fixes this.The general idea is to memoize a function that points to a ref that points to the most recent function.
It's a dirty hack that may also cause problems in concurrent mode, but for now, it's what facebook recommends:How to read an often-changing value from useCallback
const useEventCallback = (fn) => { const ref = useRef(fn) useEffect(() => { ref.current = fn }) return useCallback((...args) => { ref.current(...args) }, [ref])}const onMouseMove = useEventCallback((e) => { if (!isSwipe(e)) { onMouseUp(e) }})const onMouseUp = useEventCallback((e) => { document.removeEventListener('mousemove', onMouseMove)})