Check if a function is created by useCallback?

Viewed 1030

I have hooks that accept functions as arguments. If the functions change unnecessarily, then it could cause extra re-renders. I want to ensure that the functions are wrapped with useCallback.

I.e. here's a simplified version:

function useApi({ onSuccess }) {
  if (process.env.NODE_ENV !== 'production' && !isUseCallback(onSuccess)) {
    throw new Error();
  }

  useEffect(() => {
    fetch(...)
      .then(res => res.json())
      .then(onSuccess);
  }, [onSuccess]);
}

If onSuccess changes unnecessarily, then it'll call the API unnecessarily. Is it possible to have the function isUseCallback? I think I'd have to write a custom useCallback that wraps React's useCallback.

2 Answers

You can wrap the callback in a useRef() to ensure the useEffect() callback has a reference to the most recent onSuccess:

function useApi({ onSuccess }) {
  const ref = useRef();

  ref.current = onSuccess;

  useEffect(() => {
    fetch(...)
      .then(res => res.json())
      .then(val => ref.current(val));
  }, []);
}

It's important that you write .then(val => ref.current(val)) instead of just .then(ref.current) so that the property access ref.current doesn't occur until the fetch() resolves. That way ref.current is equal to the most recent onSuccess passed to useApi().

I solved this using Typescript:

type UseCallback<T extends (...args: any[]) => any> =
  ((...args: Parameters<T>) => ReturnType<T>) & { __IS_USE_CALLBACK?: undefined };

declare function useCallback<T extends (...args: any[]) => any>(
  callback: T,
  deps: ReadonlyArray<any>,
): UseCallback<T>;

.

function useApi(onSuccess: UseCallback<() => any>): any;

useApi(() => {}); // Error
useApi(useCallback(() => {})); // No error
Related