Skip to content

Commit

Permalink
execute the latest callback
Browse files Browse the repository at this point in the history
  • Loading branch information
xnimorz committed Aug 17, 2019
1 parent 171101d commit eca14cc
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 20 deletions.
13 changes: 7 additions & 6 deletions src/useDebouncedCallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export default function useDebouncedCallback<T extends (...args: any[]) => any>(
const functionTimeoutHandler = useRef(null);
const isComponentUnmounted: { current: boolean } = useRef(false);

const debouncedFunction = callback;
const debouncedFunction = useRef(callback);
debouncedFunction.current = callback;

const cancelDebouncedCallback: () => void = useCallback(() => {
clearTimeout(functionTimeoutHandler.current);
Expand All @@ -40,7 +41,7 @@ export default function useDebouncedCallback<T extends (...args: any[]) => any>(
clearTimeout(functionTimeoutHandler.current);

if (!functionTimeoutHandler.current && leading && !wasLeadingCalled.current) {
debouncedFunction(...args);
debouncedFunction.current(...args);
wasLeadingCalled.current = true;
return;
}
Expand All @@ -49,7 +50,7 @@ export default function useDebouncedCallback<T extends (...args: any[]) => any>(
cancelDebouncedCallback();

if (!isComponentUnmounted.current) {
debouncedFunction(...args);
debouncedFunction.current(...args);
}
}, delay);

Expand All @@ -59,12 +60,12 @@ export default function useDebouncedCallback<T extends (...args: any[]) => any>(
cancelDebouncedCallback();

if (!isComponentUnmounted.current) {
debouncedFunction.apply(null, args);
debouncedFunction.current.apply(null, args);
}
}, maxWait);
}
},
[debouncedFunction, maxWait, delay, cancelDebouncedCallback, leading]
[maxWait, delay, cancelDebouncedCallback, leading]
);

const callPending = () => {
Expand All @@ -73,7 +74,7 @@ export default function useDebouncedCallback<T extends (...args: any[]) => any>(
return;
}

debouncedFunction.apply(null, maxWaitArgs.current);
debouncedFunction.current.apply(null, maxWaitArgs.current);
cancelDebouncedCallback();
};

Expand Down
64 changes: 50 additions & 14 deletions test/useDebouncedCallback.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Enzyme from 'enzyme';
import { useEffect, useCallback } from 'react';
import { useEffect, useCallback, useRef } from 'react';
import * as React from 'react';
import useDebouncedCallback from '../src/useDebouncedCallback';
import { act } from 'react-dom/test-utils';
Expand Down Expand Up @@ -30,7 +30,7 @@ describe('useDebouncedCallback', () => {
const callback = jest.fn();

function Component() {
const [debouncedCallback] = useDebouncedCallback(callback, 1000, {leading: true});
const [debouncedCallback] = useDebouncedCallback(callback, 1000, { leading: true });
debouncedCallback();
return null;
}
Expand All @@ -49,7 +49,7 @@ describe('useDebouncedCallback', () => {
const callback = jest.fn();

function Component() {
const [debouncedCallback] = useDebouncedCallback(callback, 1000, {leading: true});
const [debouncedCallback] = useDebouncedCallback(callback, 1000, { leading: true });
debouncedCallback();
debouncedCallback();
return null;
Expand All @@ -64,12 +64,12 @@ describe('useDebouncedCallback', () => {

expect(callback.mock.calls.length).toBe(2);
});

it('will call a second leading callback if no debounced callbacks are pending', () => {
const callback = jest.fn();

function Component() {
const [debouncedCallback] = useDebouncedCallback(callback, 1000, {leading: true});
const [debouncedCallback] = useDebouncedCallback(callback, 1000, { leading: true });
debouncedCallback();
debouncedCallback();
setTimeout(() => {
Expand Down Expand Up @@ -380,37 +380,73 @@ describe('useDebouncedCallback', () => {
});
});

it('will change reference to debouncedCallback if dependency from deps array is changed', () => {
it('will change reference to debouncedCallback timeout is changed', () => {
expect.assertions(3);
let debouncedCallbackCached = null;
let textCached = null;
let timeoutCached = null;

function Component({ text }) {
const [debouncedCallback] = useDebouncedCallback(useCallback(() => {}, [text]), 500);
function Component({ text, timeout }) {
const [debouncedCallback] = useDebouncedCallback(useCallback(() => {}, [text]), timeout);

if (debouncedCallbackCached) {
if (textCached === text) {
if (timeoutCached === timeout) {
expect(debouncedCallback).toBe(debouncedCallbackCached);
} else {
expect(debouncedCallback).not.toBe(debouncedCallbackCached);
}
}
debouncedCallbackCached = debouncedCallback;
textCached = text;
timeoutCached = timeout;

return <span>{text}</span>;
}
const tree = Enzyme.mount(<Component text="one" />);
const tree = Enzyme.mount(<Component timeout={500} text="one" />);

expect(tree.text()).toBe('one');

act(() => {
tree.setProps({ text: 'one' });
tree.setProps({ timeout: 500 });
});

act(() => {
tree.setProps({ text: 'two' });
tree.setProps({ text: 1000 });
});
});

it('will call the latest callback', () => {
expect.assertions(1);

function Component({ callback }) {
const [debouncedCallback] = useDebouncedCallback(callback, 500);
const counter = useRef(1);

useEffect(() => {
// this useEffect should be called only once
debouncedCallback(counter.current);

counter.current = counter.current + 1;
}, [debouncedCallback]);

return null;
}
const tree = Enzyme.mount(
<Component
callback={() => {
throw new Error("This callback shouldn't be executed");
}}
/>
);

act(() => {
tree.setProps({
callback: (counter) => {
// This callback should be called with counter === 1
expect(counter).toBe(1);
},
});
});

jest.runTimersToTime(500);
});

it('will change reference to debouncedCallback if maxWait or delay option is changed', () => {
Expand Down

0 comments on commit eca14cc

Please sign in to comment.