diff --git a/packages/tiny-react-hooks/src/index.ts b/packages/tiny-react-hooks/src/index.ts index d2b6108..d43351e 100644 --- a/packages/tiny-react-hooks/src/index.ts +++ b/packages/tiny-react-hooks/src/index.ts @@ -1 +1,2 @@ export * from './useDisclosure'; +export * from './useStableCallback'; diff --git a/packages/tiny-react-hooks/src/useStableCallback/index.ts b/packages/tiny-react-hooks/src/useStableCallback/index.ts new file mode 100644 index 0000000..9e86eaa --- /dev/null +++ b/packages/tiny-react-hooks/src/useStableCallback/index.ts @@ -0,0 +1 @@ +export * from './useStableCallback'; diff --git a/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.demo.tsx b/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.demo.tsx new file mode 100644 index 0000000..beef0be --- /dev/null +++ b/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.demo.tsx @@ -0,0 +1,15 @@ +import { useStableCallback } from './useStableCallback'; + +interface Props { + count: number; +} + +export default function Component({ + count, +}: Props) { + const sum = useStableCallback(() => { + console.log(count * 10); + }); + + return ; +} diff --git a/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.md b/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.md new file mode 100644 index 0000000..fb35636 --- /dev/null +++ b/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.md @@ -0,0 +1 @@ +A hook that keep the callback always stable and do not need to add to dependencies of other hooks \ No newline at end of file diff --git a/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.test.ts b/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.test.ts new file mode 100644 index 0000000..15705d0 --- /dev/null +++ b/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.test.ts @@ -0,0 +1,27 @@ +import { renderHook } from '@testing-library/react'; + +import { useStableCallback } from './useStableCallback'; + +describe('useStableCallback()', () => { + it('should return the callback', () => { + const mockFn = vi.fn(() => 1); + const { result } = renderHook(() => useStableCallback(mockFn)); + + expect(result.current()).toBe(1); + expect(typeof result.current).toBe('function'); + }); + + describe('when fn is changed', () => { + it('should return the correct callback', () => { + const mockFn1 = vi.fn(() => 1); + const mockFn2 = vi.fn(() => 2); + const { result, rerender } = renderHook( + ({ fn }) => useStableCallback(fn), + { initialProps: { fn: mockFn1 } }, + ); + expect(result.current()).toBe(1); + rerender({ fn: mockFn2 }); + expect(result.current()).toBe(2); + }); + }); +}); diff --git a/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.ts b/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.ts new file mode 100644 index 0000000..317c3ab --- /dev/null +++ b/packages/tiny-react-hooks/src/useStableCallback/useStableCallback.ts @@ -0,0 +1,30 @@ +import { useEffect, useMemo, useRef } from 'react'; + +/** Hook return type */ +type UseStableCallbackReturnType unknown> = T; + +/** + * Custom hook that ... + * @param {Function} [fn] - your function need to be stable + * @returns {UseStableCallbackReturnType} return the the stable callback that will apply the newest function + * @public + * @example + * ```tsx + * const stableFn = useStableCallback((a: number, b: number) => { + * return a + b; + * }); + * + * console.log(stableFn(1, 2)); // 3 + * ``` + */ +export function useStableCallback unknown>( + fn: T, +): UseStableCallbackReturnType { + const callbackRef = useRef(fn); + + useEffect(() => { + callbackRef.current = fn; + }, [fn]); + + return useMemo(() => ((...args) => callbackRef.current(...args)) as T, []); +}