Skip to content

Commit

Permalink
omit useDebounceCallback func from dependency array
Browse files Browse the repository at this point in the history
  • Loading branch information
therour committed Feb 22, 2024
1 parent 61a3b68 commit 3f99d23
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,27 @@ describe('useDebounceCallback()', () => {
// The callback should be invoked immediately after flushing
expect(debouncedCallback).toHaveBeenCalled()
})

it('should have pending state', () => {
const delay = 500
const debouncedCallback = vitest.fn()
const { result } = renderHook(() =>
useDebounceCallback(debouncedCallback, delay),
)

act(() => {
result.current('argument')
})

// The callback must be pending before invoked
expect(debouncedCallback).not.toHaveBeenCalled()
expect(result.current.isPending()).toBe(true)

// Fast forward time
vitest.advanceTimersByTime(500)

// The callback must be not pending after invoked
expect(debouncedCallback).toHaveBeenCalled()
expect(result.current.isPending()).toBe(false)
})
})
48 changes: 33 additions & 15 deletions packages/usehooks-ts/src/useDebounceCallback/useDebounceCallback.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef } from 'react'
import { useMemo, useRef } from 'react'

import debounce from 'lodash.debounce'

Expand Down Expand Up @@ -79,18 +79,37 @@ export function useDebounceCallback<T extends (...args: any) => ReturnType<T>>(
delay = 500,
options?: DebounceOptions,
): DebouncedState<T> {
const debouncedFunc = useRef<ReturnType<typeof debounce>>()
const pending = useRef<boolean>(false)

useUnmount(() => {
if (debouncedFunc.current) {
debouncedFunc.current.cancel()
}
})
const funcRef = useRef<T>(func)
funcRef.current = func

const debounced = useMemo(() => {
const debouncedFuncInstance = debounce(func, delay, options)
const debounceOptions =
options?.leading !== undefined ||
options?.trailing !== undefined ||
options?.maxWait !== undefined
? {
leading: options?.leading,
trailing: options?.trailing,
maxWait: options?.maxWait,
}
: undefined

const wrappedFunc: DebouncedState<T> = (...args: Parameters<T>) => {
const debouncedFuncInstance = debounce(
(...args: unknown[]) => {
try {
return funcRef.current(...args)
} finally {
pending.current = false
}
},
delay,
debounceOptions,
)

const wrappedFunc: DebouncedState<T> = (...args) => {
pending.current = true
return debouncedFuncInstance(...args)
}

Expand All @@ -99,20 +118,19 @@ export function useDebounceCallback<T extends (...args: any) => ReturnType<T>>(
}

wrappedFunc.isPending = () => {
return !!debouncedFunc.current
return pending.current
}

wrappedFunc.flush = () => {
return debouncedFuncInstance.flush()
}

return wrappedFunc
}, [func, delay, options])
}, [delay, options?.leading, options?.trailing, options?.maxWait])

// Update the debounced function ref whenever func, wait, or options change
useEffect(() => {
debouncedFunc.current = debounce(func, delay, options)
}, [func, delay, options])
useUnmount(() => {
debounced.cancel()
})

return debounced
}

0 comments on commit 3f99d23

Please sign in to comment.