Skip to content

Commit d1ead47

Browse files
committed
feat(useDebouncedCallback): return value from debounced function
1 parent d0855bd commit d1ead47

File tree

3 files changed

+70
-10
lines changed

3 files changed

+70
-10
lines changed

src/useDebouncedCallback.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,42 @@ export interface UseDebouncedCallbackOptions {
99
maxWait?: number
1010
}
1111

12+
export interface UseDebouncedCallbackOptionsLeading
13+
extends UseDebouncedCallbackOptions {
14+
leading: true
15+
}
16+
1217
/**
1318
* Creates a debounced function that will invoke the input function after the
1419
* specified wait.
1520
*
1621
* @param fn a function that will be debounced
1722
* @param waitOrOptions a wait in milliseconds or a debounce configuration
1823
*/
19-
export default function useDebouncedCallback<
20-
TCallback extends (...args: any[]) => any,
21-
>(
24+
function useDebouncedCallback<TCallback extends (...args: any[]) => any>(
25+
fn: TCallback,
26+
options: UseDebouncedCallbackOptionsLeading,
27+
): (...args: Parameters<TCallback>) => ReturnType<TCallback>
28+
29+
/**
30+
* Creates a debounced function that will invoke the input function after the
31+
* specified wait.
32+
*
33+
* @param fn a function that will be debounced
34+
* @param waitOrOptions a wait in milliseconds or a debounce configuration
35+
*/
36+
function useDebouncedCallback<TCallback extends (...args: any[]) => any>(
37+
fn: TCallback,
38+
waitOrOptions: number | UseDebouncedCallbackOptions,
39+
): (...args: Parameters<TCallback>) => ReturnType<TCallback> | undefined
40+
41+
function useDebouncedCallback<TCallback extends (...args: any[]) => any>(
2242
fn: TCallback,
2343
waitOrOptions: number | UseDebouncedCallbackOptions,
24-
): (...args: Parameters<TCallback>) => void {
44+
): (...args: Parameters<TCallback>) => ReturnType<TCallback> | undefined {
2545
const lastCallTimeRef = useRef<number | null>(null)
2646
const lastInvokeTimeRef = useRef(0)
47+
const returnValueRef = useRef<ReturnType<TCallback>>()
2748

2849
const isTimerSetRef = useRef(false)
2950
const lastArgsRef = useRef<unknown[] | null>(null)
@@ -50,10 +71,11 @@ export default function useDebouncedCallback<
5071
isTimerSetRef.current = true
5172
timeout.set(timerExpired, wait)
5273

53-
// Invoke the leading edge.
54-
if (leading) {
55-
invokeFunc(time)
74+
if (!leading) {
75+
return returnValueRef.current
5676
}
77+
78+
return invokeFunc(time)
5779
}
5880

5981
function trailingEdge(time: number) {
@@ -66,6 +88,7 @@ export default function useDebouncedCallback<
6688
}
6789

6890
lastArgsRef.current = null
91+
return returnValueRef.current
6992
}
7093

7194
function timerExpired() {
@@ -94,7 +117,9 @@ export default function useDebouncedCallback<
94117
lastArgsRef.current = null
95118
lastInvokeTimeRef.current = time
96119

97-
return fn(...args)
120+
const retValue = fn(...args)
121+
returnValueRef.current = retValue
122+
return retValue
98123
}
99124

100125
function shouldInvoke(time: number) {
@@ -136,6 +161,10 @@ export default function useDebouncedCallback<
136161
isTimerSetRef.current = true
137162
setTimeout(timerExpired, wait)
138163
}
164+
165+
return returnValueRef.current
139166
}
140167
}, [fn, wait, maxWait, leading, trailing])
141168
}
169+
170+
export default useDebouncedCallback

test/useDebouncedCallback.test.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,39 @@ describe('useDebouncedCallback', () => {
133133
expect(callback).toHaveBeenCalledTimes(2)
134134
})
135135

136+
it('Subsequent calls to the debounced function return the result of the last func invocation.', () => {
137+
const callback = jest.fn(() => 42)
138+
139+
const { result } = renderHook(() => useDebouncedCallback(callback, 1000))
140+
141+
const retVal = result.current()
142+
expect(callback).toHaveBeenCalledTimes(0)
143+
expect(retVal).toBeUndefined()
144+
145+
act(() => {
146+
jest.runAllTimers()
147+
})
148+
expect(callback).toHaveBeenCalledTimes(1)
149+
150+
const subsequentResult = result.current()
151+
152+
expect(callback).toHaveBeenCalledTimes(1)
153+
expect(subsequentResult).toBe(42)
154+
})
155+
156+
it('Returns the value when leading immediately', () => {
157+
const callback = jest.fn(() => 42)
158+
159+
const { result } = renderHook(() =>
160+
useDebouncedCallback(callback, { wait: 1000, leading: true }),
161+
)
162+
163+
const retVal = result.current()
164+
165+
expect(callback).toHaveBeenCalledTimes(1)
166+
expect(retVal).toEqual(42)
167+
})
168+
136169
it("won't call both on the leading edge and on the trailing edge if leading and trailing are set up to true and function call is only once", () => {
137170
const callback = jest.fn()
138171

test/useDebouncedValue.test.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ describe('useDebouncedValue', () => {
5353
rerender(<Wrapper text={'Hello world'} />)
5454
})
5555

56-
// value should be set immediately by first leading call
57-
5856
expect(getByText('Hello world')).toBeTruthy()
5957

6058
act(() => {

0 commit comments

Comments
 (0)