Search code examples
reactjstypescriptreact-hooks

why use 'ref' when you use useCallback instead of using useCallback directly


I'm working on React project, and I was investigating some libraries. and I found they used 'useCallback" differently from what I've used. Below is that code part. I still think this code has no difference from using "useCallback" in a direct way though

// Saves incoming handler to the ref in order to avoid "useCallback hell"
export function useEventCallback<T, K>(handler?: (value: T, event: K) => void): (value: T, event: K) => void {
  const callbackRef = useRef(handler);

  useEffect(() => {
    callbackRef.current = handler;
  });

  return useCallback((value: T, event: K) => callbackRef.current && callbackRef.current(value, event), []);
}

so my question is that what does mean by 'useCallback hell'? and what is the advantage of using "useCallback" such that way?

// BTW : I found a similar example on react documentation. but I still couldn't understand https://en.reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback


Solution

  • When you do a normal useCallback, you must pass in a dependency array containing the variables your function uses. When one of them changes, the memoization breaks. This is fine in many cases, but sometimes your memoization is breaking all the time (because you depend on values that change all the time). When this happens, useCallback provides little or no benefit.

    The code you've shown has the goal that the memoization never breaks, even if you have complicated dependencies. Note that when it calls useCallback, it passes in an empty dependency array []. That's combined with using a ref to be able to keep track of what the latest handler is. Then when the function is eventually called, it will check the ref for the latest handler and call it. That latest handler has the latest values in its closure, so it behaves as expected.

    This code does achieve its goal of never breaking memoization. However, it needs to be used carefully. If you are using react's concurrent rendering, and you call the function returned by useEventCallback during rendering, you can get some unexpected results. It's only safe to call the function outside of rendering, such as in an event callback, which is why they named it useEventCallback.