Skip to content

Commit 12658ee

Browse files
authored
fix(useDebouncedCallback): make invoked function to be updated with deps (#1510)
fix: #1357
1 parent a2a310e commit 12658ee

File tree

3 files changed

+42
-6
lines changed

3 files changed

+42
-6
lines changed

src/useDebouncedCallback/__docs__/story.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export function useDebouncedCallback<Args extends any[], This>(
2929
#### Arguments
3030

3131
- **callback** _`(...args: T) => unknown`_ - function that will be debounced.
32-
- **deps** _`React.DependencyList`_ - dependencies list when to update callback.
32+
- **deps** _`React.DependencyList`_ - dependencies list when to update callback. It also replaces
33+
invoked callback for scheduled debounced invocations.
3334
- **delay** _`number`_ - debounce delay.
3435
- **maxWait** _`number`_ _(default: `0`)_ - The maximum time `callback` is allowed to be delayed
3536
before it's invoked. `0` means no max wait.

src/useDebouncedCallback/__tests__/dom.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,32 @@ describe('useDebouncedCallback', () => {
152152
jest.advanceTimersByTime(200);
153153
expect(cb).toHaveBeenCalledTimes(1);
154154
});
155+
156+
it('should call updated function only when deps changed', () => {
157+
const cb = jest.fn();
158+
159+
const { result, rerender } = renderHook(
160+
({ cb, deps }: { cb: () => void; deps: any[] }) => useDebouncedCallback(cb, deps, 200, 200),
161+
{
162+
initialProps: {
163+
cb() {},
164+
deps: [0],
165+
},
166+
}
167+
);
168+
169+
result.current();
170+
171+
rerender({ cb, deps: [0] });
172+
173+
jest.advanceTimersByTime(200);
174+
expect(cb).toHaveBeenCalledTimes(0);
175+
176+
result.current();
177+
178+
rerender({ cb, deps: [1] });
179+
180+
jest.advanceTimersByTime(200);
181+
expect(cb).toHaveBeenCalledTimes(1);
182+
});
155183
});

src/useDebouncedCallback/index.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type DependencyList, useMemo, useRef } from 'react';
1+
import { type DependencyList, useEffect, useMemo, useRef } from 'react';
22
import { useUnmountEffect } from '../useUnmountEffect/index.js';
33

44
export type DebouncedFunction<Fn extends (...args: any[]) => any> = (
@@ -10,7 +10,8 @@ export type DebouncedFunction<Fn extends (...args: any[]) => any> = (
1010
* Makes passed function debounced, otherwise acts like `useCallback`.
1111
*
1212
* @param callback Function that will be debounced.
13-
* @param deps Dependencies list when to update callback.
13+
* @param deps Dependencies list when to update callback. It also replaces invoked
14+
* callback for scheduled debounced invocations.
1415
* @param delay Debounce delay.
1516
* @param maxWait The maximum time `callback` is allowed to be delayed before
1617
* it's invoked. 0 means no max wait.
@@ -23,6 +24,7 @@ export function useDebouncedCallback<Fn extends (...args: any[]) => any>(
2324
): DebouncedFunction<Fn> {
2425
const timeout = useRef<ReturnType<typeof setTimeout>>();
2526
const waitTimeout = useRef<ReturnType<typeof setTimeout>>();
27+
const cb = useRef(callback);
2628
const lastCall = useRef<{ args: Parameters<Fn>; this: ThisParameterType<Fn> }>();
2729

2830
const clear = () => {
@@ -40,18 +42,23 @@ export function useDebouncedCallback<Fn extends (...args: any[]) => any>(
4042
// Cancel scheduled execution on unmount
4143
useUnmountEffect(clear);
4244

45+
useEffect(() => {
46+
cb.current = callback;
47+
// eslint-disable-next-line react-hooks/exhaustive-deps
48+
}, deps);
49+
4350
return useMemo(() => {
4451
const execute = () => {
52+
clear();
53+
4554
// Barely possible to test this line
4655
/* istanbul ignore next */
4756
if (!lastCall.current) return;
4857

4958
const context = lastCall.current;
5059
lastCall.current = undefined;
5160

52-
callback.apply(context.this, context.args);
53-
54-
clear();
61+
cb.current.apply(context.this, context.args);
5562
};
5663

5764
const wrapped = function (this, ...args) {

0 commit comments

Comments
 (0)