Home > front end >  Memoize function argument in custom hook
Memoize function argument in custom hook

Time:09-22

I'm building a custom hook that accepts a function. If the function will not change between re-renders/updates, where should I memoize the function?

OPTION 1

const useCustomHook = (callback) => {
  const callbackRef = useRef();
  callbackRef.current = callback;

  // No effect, this will be re-created every time this hook is called
  // because a new instance of callback is being passed
  const callbackWrapper = useCallback(() => {
    if (callbackRef.current) {
      callbackRef.current();
    }
  }, [callbackRef]);

  // use callbackWrapper
}

const Component = () => {
  // New instance of passed callback will be created each time this component re-renders
  useCustomHook(() => {
    console.log(`I'm being passed to the hook`);
  });

  // ...
  // ...
  return <div></div>;
}

OPTION 2

const useCustomHook = (callback) => {
  // Callback is already memoized
  const callbackRef = useRef();
  callbackRef.current = callback;

  // use callbackRef
}

const Component = () => {
  // Memoized function passed, but
  // 1. Is this allowed?
  // 2. Requires more effort by users of the hook
  useCustomHook(useCallback(() => {
    console.log(`I'm being passed to the hook`);
  }, []));

  // ...
  // ...
  return <div></div>;
}

Option 2 seems more valid but it requires users of the custom hook to first enclose their function in a useCallback() hook. Is there an alternative way where users don't need to enclose the passed function in useCallback()?

CodePudding user response:

I would probably use the useEffect hook to "memoize" the callback to the React ref.

const useCustomHook = (callback) => {
  const callbackRef = useRef();

  useEffect(() => {
    // only update callback saved in ref if callback updates
    callbackRef.current = callback;
  }, [callback]);

  // provide stable callback that always calls latest
  // callback stored in ref
  const callbackWrapper = useCallback(() => {
    if (callbackRef.current) {
      callbackRef.current();
    }
  }, []);

  // use callbackWrapper
}

Or use useMemo or useCallback directly, but at this point you are essentially just redefining useMemo or useCallback in your custom hook. In this case, pass an additional dependency array argument.

const useCustomHook = (callback, deps) => {
  const callbackWrapper = useMemo(() => callback, deps);

  // use callbackWrapper
}

This being said, you will likely want to be in the habit of memoizing callbacks that are being passed down before they are passed, in order to guaranteed provided stable references. This way you won't need to go through the rigamarole of the weirdness of your custom hook.

CodePudding user response:

Another other way is to declare the callback outside of the component body ( but I don't recommend that, It can create some edge cases )

function cb () {
  console.log(`I'm being passed to the hook`);
}

const Component = () => {

  useCustomHook(cb);

  // ...
  // ...
  return <div></div>;
}

memoize the function in you want on top level component body and then pass it to your custom hook like this:

const Component = () => {
  // hooks should be called on top level function body
  const cb = useCallback(() => {
    console.log(`I'm being passed to the hook`);
  }, []);

  useCustomHook(cb);

  // ...
  // ...
  return <div></div>;
}

  • Related