Skip to content

Commit

Permalink
return object instead of array from useDebouncedCallback. Add pending fn
Browse files Browse the repository at this point in the history
  • Loading branch information
xnimorz committed Sep 19, 2020
1 parent f9f4cb3 commit 1b4ac04
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 93 deletions.
16 changes: 6 additions & 10 deletions src/useDebounce.ts
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import useDebouncedCallback from './useDebouncedCallback';
import useDebouncedCallback, { ControlFunctions } from './useDebouncedCallback';

function valueEquality<T>(left: T, right: T): boolean {
return left === right;
Expand All @@ -9,24 +9,20 @@ export default function useDebounce<T>(
value: T,
delay: number,
options?: { maxWait?: number; leading?: boolean; trailing?: boolean; equalityFn?: (left: T, right: T) => boolean }
): [T, () => void, () => void] {
): [T, ControlFunctions] {
const eq = options && options.equalityFn ? options.equalityFn : valueEquality;

const [state, dispatch] = useState(value);
const [callback, cancel, callPending] = useDebouncedCallback(
useCallback((value: T) => dispatch(value), []),
delay,
options
);
const debounced = useDebouncedCallback(useCallback((value: T) => dispatch(value), []), delay, options);
const previousValue = useRef(value);

useEffect(() => {
// We need to use this condition otherwise we will run debounce timer for the first render (including maxWait option)
if (!eq(previousValue.current, value)) {
callback(value);
debounced.callback(value);
previousValue.current = value;
}
}, [value, callback, eq]);
}, [value, debounced.callback, eq]);

return [state, cancel, callPending];
return [state, { cancel: debounced.cancel, pending: debounced.pending, flush: debounced.flush }];
}
31 changes: 27 additions & 4 deletions src/useDebouncedCallback.ts
@@ -1,16 +1,26 @@
import { useRef, useCallback, useEffect } from 'react';
import { useRef, useCallback, useEffect, useMemo } from 'react';

export interface Options {
maxWait?: number;
leading?: boolean;
trailing?: boolean;
}

export interface ControlFunctions {
cancel: () => void;
flush: () => void;
pending: () => boolean;
}

export interface DebouncedState<T extends unknown[]> extends ControlFunctions {
callback: (...args: T) => unknown;
}

export default function useDebouncedCallback<T extends unknown[]>(
func: (...args: T) => unknown,
wait: number,
options: Options = { leading: false, trailing: true }
): [(...args: T) => void, () => void, () => void] {
): DebouncedState<T> {
const lastCallTime = useRef(undefined);
const lastInvokeTime = useRef(0);
const timerId = useRef(undefined);
Expand Down Expand Up @@ -178,6 +188,19 @@ export default function useDebouncedCallback<T extends unknown[]>(
[invokeFunc, leadingEdge, maxing, shouldInvoke, startTimer, timerExpired, wait]
);

// At the moment, we use 3 args array so that we save backward compatibility
return [debounced, cancel, flush];
const pending = useCallback(() => {
return timerId.current !== undefined;
}, []);

const debouncedState: DebouncedState<T> = useMemo(
() => ({
callback: debounced,
cancel,
flush,
pending,
}),
[debounced, cancel, flush, pending]
);

return debouncedState;
}
12 changes: 6 additions & 6 deletions test/useDebounce.test.tsx
Expand Up @@ -71,8 +71,8 @@ describe('useDebounce', () => {

it('will cancel value when cancel method is called', () => {
function Component({ text }) {
const [value, cancelValue] = useDebounce(text, 1000);
setTimeout(cancelValue, 500);
const [value, fn] = useDebounce(text, 1000);
setTimeout(fn.cancel, 500);
return <div>{value}</div>;
}
const tree = Enzyme.mount(<Component text={'Hello'} />);
Expand Down Expand Up @@ -121,9 +121,9 @@ describe('useDebounce', () => {

it('should cancel maxWait callback', () => {
function Component({ text }) {
const [value, cancel] = useDebounce(text, 500, { maxWait: 600 });
const [value, fn] = useDebounce(text, 500, { maxWait: 600 });
if (text === 'Right value') {
cancel();
fn.cancel();
}
return <div>{value}</div>;
}
Expand Down Expand Up @@ -276,8 +276,8 @@ describe('useDebounce', () => {
it('should setup new value immediately if callPending is called', () => {
let callPending;
function Component({ text }) {
const [value, , _callPending] = useDebounce(text, 1000);
callPending = _callPending;
const [value, fn] = useDebounce(text, 1000);
callPending = fn.flush;

return <div>{value}</div>;
}
Expand Down

0 comments on commit 1b4ac04

Please sign in to comment.