0

This hook takes a function that created by using useCallback (which will update every time its dependencies do) and returns a function that calls the updated callback while not updating itself. While in some cases you might care when a callback is updated, in some others (like event handlers) you likely don't. Also, when passed to a memoized child that doesn't care when the callback updates, you can save an unnecessary render, as demonstrated in this here demo.

import {useRef, useEffect, useCallback} from "react";

export default function useDecoupledCallback(callback) {
  const callbackRef = useRef(callback);

  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);

  return useCallback((...args) => 
    callbackRef.current(...args), [callbackRef]);
}

Does React remove the old event listeners and replace them every time their dependencies change, or does it do something like what this hook does internally? And if not, is React so fast that it doesn't make a difference?

Assuming that it doesn't make a difference in performance, can this method be safely used to shorten the dependency array of dependant hooks?

Thank you.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
BanForFun
  • 752
  • 4
  • 15
  • I'm also curious if there are any pitfalls(cannot imagine any so far). In case you question will be closed as opinion-based(that may happen, yes), suggest to start discussion on reddit, in r/reactjs – skyboyer Oct 18 '21 at 07:12

1 Answers1

0

the explanation is not tied to react as much as it's tied to the javascript language.

in javascript when you create a function you actually create a variable with the reference to the actual function in the memory. same with the objects. so the callback is nothing but a reference to the actual function

the problem with react is that whenever a component runs the functions are re-evaluated and the previous functions (which no variable points to them) are garbage collected and new functions with new references are created.

the solution to this is memorization. useMemo and useCallback accept a dependency array that checks if the function should be re-evaluated. if the references (in case of objects being passed to the dependency array, or value check in case of primitives) are the same as before the function would not be re-evaluated and the reference to the function will be the same as before.

using useRef keeps a pointer to the reference which stops it from being garbage collected. and since the current is actually a getter function you can reference the value returned from useRef hook in a function and update it whenever you want without needing to reevaluate the function that is using the reference. (you are passing the reference to useRef. and use ref returns whatever reference it's holding when it's called.)

i consider this an optimization but only if the cost of reevaluating the function is high. same goes for useCallback and useMemo. it's easy to throw them around everywhere. use them wisely.

Omid
  • 438
  • 4
  • 11
  • Not just optimization-related, but also shift in logic. Let's imagine `useEffect` that should call `props.onChange` each time when `counter` updates. Normally we need to add both to dependencies array. But it means effect will run even when counter is the same - just because `onChange` is referentially different. And `useCallback` inside that component will not help changing this. Only updating every single parent will. Or this trick with callback in ref. – skyboyer Oct 18 '21 at 14:30