Skip to content

Instantly share code, notes, and snippets.

@JakeCoxon
Last active June 27, 2022 07:03
Show Gist options
  • Save JakeCoxon/c7ebf6e6496f8468226fd36b596e1985 to your computer and use it in GitHub Desktop.
Save JakeCoxon/c7ebf6e6496f8468226fd36b596e1985 to your computer and use it in GitHub Desktop.
import { useRef, useCallback, useEffect } from 'react';
// A callback that always closes over the latest data but keeps the same
// identity and will not be called after component unmounts
const useStableCallback = callback => {
const callbackRef = useRef();
const memoCallback = useCallback(
(...args) => callbackRef.current && callbackRef.current(...args),
[],
);
useEffect(() => {
callbackRef.current = callback;
return () => (callbackRef.current = undefined);
});
return memoCallback;
};
export default useStableCallback;
import { renderHook } from 'react-hooks-testing-library';
it('keeps identity', () => {
const useUberLinking = require('@zego/hooks/useStableCallback').default;
const hook = renderHook(callback => useUberLinking(callback), {
initialProps: x => x + 3,
});
const initialIdentity = hook.result.current;
expect(hook.result.current).toEqual(expect.any(Function));
expect(hook.result.current(4)).toBe(7);
hook.rerender(x => x + 10);
expect(hook.result.current(4)).toBe(14);
// Identity is still the same
expect(hook.result.current).toBe(initialIdentity);
});
it('does not get called after unmounting', () => {
const useUberLinking = require('@zego/hooks/useStableCallback').default;
const mockFn = jest.fn();
const hook = renderHook(callback => useUberLinking(callback), {
initialProps: mockFn,
});
hook.unmount();
hook.result.current();
expect(mockFn).not.toHaveBeenCalled();
});
@gorhom
Copy link

gorhom commented Jun 17, 2020

thanks for sharing :)

@mebtte
Copy link

mebtte commented Jun 27, 2022

Maybe there don't need useEffect:

import { useRef, useCallback } from 'react';

function useEvent<Callback extends (...args: unknown[]) => unknown>(
  callback: Callback,
) {
  const callbackRef = useRef<Callback>();
  callbackRef.current = callback;

  const memoCallback = useCallback(
    (...args: Parameters<Callback>) => callbackRef.current!(...args),
    [],
  );

  return memoCallback;
}

export default useEvent;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment