Skip to content

Commit 70cf449

Browse files
authored
feat: new useStableCallback hook (#5)
* feat: add useStableCallback docs * feat: implement useStableCallback * test: add tests for useStableCallback * feat: export hook * docs: add example for useStableCallback
1 parent 6d28cab commit 70cf449

6 files changed

Lines changed: 75 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './useDisclosure';
2+
export * from './useStableCallback';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useStableCallback';
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useStableCallback } from './useStableCallback';
2+
3+
interface Props {
4+
count: number;
5+
}
6+
7+
export default function Component({
8+
count,
9+
}: Props) {
10+
const sum = useStableCallback(() => {
11+
console.log(count * 10);
12+
});
13+
14+
return <button type="button" role="button" onClick={sum}>Click me and see the console</button>;
15+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A hook that keep the callback always stable and do not need to add to dependencies of other hooks
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { renderHook } from '@testing-library/react';
2+
3+
import { useStableCallback } from './useStableCallback';
4+
5+
describe('useStableCallback()', () => {
6+
it('should return the callback', () => {
7+
const mockFn = vi.fn(() => 1);
8+
const { result } = renderHook(() => useStableCallback(mockFn));
9+
10+
expect(result.current()).toBe(1);
11+
expect(typeof result.current).toBe('function');
12+
});
13+
14+
describe('when fn is changed', () => {
15+
it('should return the correct callback', () => {
16+
const mockFn1 = vi.fn(() => 1);
17+
const mockFn2 = vi.fn(() => 2);
18+
const { result, rerender } = renderHook(
19+
({ fn }) => useStableCallback(fn),
20+
{ initialProps: { fn: mockFn1 } },
21+
);
22+
expect(result.current()).toBe(1);
23+
rerender({ fn: mockFn2 });
24+
expect(result.current()).toBe(2);
25+
});
26+
});
27+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useEffect, useMemo, useRef } from 'react';
2+
3+
/** Hook return type */
4+
type UseStableCallbackReturnType<T extends (...args: unknown[]) => unknown> = T;
5+
6+
/**
7+
* Custom hook that ...
8+
* @param {Function} [fn] - your function need to be stable
9+
* @returns {UseStableCallbackReturnType} return the the stable callback that will apply the newest function
10+
* @public
11+
* @example
12+
* ```tsx
13+
* const stableFn = useStableCallback((a: number, b: number) => {
14+
* return a + b;
15+
* });
16+
*
17+
* console.log(stableFn(1, 2)); // 3
18+
* ```
19+
*/
20+
export function useStableCallback<T extends (...args: unknown[]) => unknown>(
21+
fn: T,
22+
): UseStableCallbackReturnType<T> {
23+
const callbackRef = useRef<T>(fn);
24+
25+
useEffect(() => {
26+
callbackRef.current = fn;
27+
}, [fn]);
28+
29+
return useMemo(() => ((...args) => callbackRef.current(...args)) as T, []);
30+
}

0 commit comments

Comments
 (0)