Skip to content

Commit

Permalink
Update use-debounced-callback.ts to add a flush method to the returne…
Browse files Browse the repository at this point in the history
…d callback as well as give an option to simply flush on unmount
  • Loading branch information
scamden committed Dec 23, 2024
1 parent 3216b36 commit 2e29135
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,59 @@ describe('@mantine/hooks/use-debounced-callback', () => {
jest.advanceTimersByTime(100);
expect(callback).toHaveBeenCalledWith(3);
});

it('can be flushed immediately', () => {
const callback = jest.fn();
const { result } = renderHook(() => useDebouncedCallback(callback, 100));
result.current(1);
result.current(2);
result.current(3);
result.current.flush();
expect(callback).toHaveBeenCalledWith(3);
});

it('can flush on unmount', () => {
const callback = jest.fn();
const { result, unmount } = renderHook(() =>
useDebouncedCallback(callback, { delay: 100, flushOnUnmount: true })
);
result.current(1);
result.current(2);
result.current(3);
unmount();
expect(callback).toHaveBeenCalledWith(3);
});

it('does not call after unmount if timer lapsed', () => {
const callback = jest.fn();
const { result, unmount } = renderHook(() =>
useDebouncedCallback(callback, { delay: 100, flushOnUnmount: false })
);
result.current(1);
unmount();
jest.advanceTimersByTime(100);
expect(callback).not.toHaveBeenCalled();
});

it('does not call on unmount if never called', () => {
const callback = jest.fn();
const { unmount } = renderHook(() =>
useDebouncedCallback(callback, { delay: 100, flushOnUnmount: true })
);
unmount();
expect(callback).not.toHaveBeenCalled();
});

it('does not call on unmount if already called and not called since', () => {
const callback = jest.fn();
const { result, unmount } = renderHook(() =>
useDebouncedCallback(callback, { delay: 100, flushOnUnmount: true })
);
result.current(1);
jest.advanceTimersByTime(100);
expect(callback).toHaveBeenCalled();
callback.mockClear();
unmount();
expect(callback).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
import { useCallback, useEffect, useRef } from 'react';
import { useCallbackRef } from '../use-callback-ref/use-callback-ref';

const noop = () => {};

export function useDebouncedCallback<T extends (...args: any[]) => any>(
callback: T,
delay: number
options: number | { delay: number; flushOnUnmount?: boolean }
) {
const delay = typeof options === 'number' ? options : options.delay;
const flushOnUnmount = typeof options === 'number' ? false : options.flushOnUnmount;
const handleCallback = useCallbackRef(callback);
const debounceTimerRef = useRef(0);
useEffect(() => () => window.clearTimeout(debounceTimerRef.current), []);

return useCallback(
(...args: Parameters<T>) => {
const lastCallback = Object.assign(
useCallback(
(...args: Parameters<T>) => {
window.clearTimeout(debounceTimerRef.current);
const flush = () => {
if (debounceTimerRef.current !== 0) {
debounceTimerRef.current = 0;
handleCallback(...args);
}
};
lastCallback.flush = flush;
debounceTimerRef.current = window.setTimeout(flush, delay);
},
[handleCallback, delay]
),
{ flush: noop }
);

useEffect(
() => () => {
window.clearTimeout(debounceTimerRef.current);
debounceTimerRef.current = window.setTimeout(() => handleCallback(...args), delay);
if (flushOnUnmount) {
lastCallback.flush();
}
},
[handleCallback, delay]
[lastCallback, flushOnUnmount]
);

return lastCallback;
}

0 comments on commit 2e29135

Please sign in to comment.