import { useRef , useEffect , useMemo } from 'react' ; export interface CallOptions { leading ? : boolean ; trailing ? : boolean ; } export interface Options extends CallOptions { maxWait ? : number ; } export interface ControlFunctions { cancel : ( ) => void ; flush : ( ) => void ; isPending : ( ) => boolean ; } /**
 * Subsequent calls to the debounced function `debounced.callback` return the result of the last func invocation.
 * Note, that if there are no previous invocations it's mean you will get undefined. You should check it in your code properly.
 */ export interface DebouncedState < T extends ( ... args : any [ ] ) => ReturnType < T > > extends ControlFunctions { ( ... args ) : ReturnType < T > | undefined ; } category param { Function } func param { number } wait param options param { boolean } options . leading param { number } options . maxWait param { boolean } options . trailing returns { Function } example export default function useDebouncedCallback < T extends ( ... args : any [ ] ) => ReturnType < T > > ( callback , wait ? , options ? ) { const lastCallTime = useRef ( null ) ; const lastInvokeTime = useRef ( 0 ) ; const timerId = useRef ( null ) ; const lastArgs = useRef < unknown [ ] > ( [ ] ) ; const lastThis = useRef < unknown > ( ) ; const result = useRef < ReturnType < T > > ( ) ; const funcRef = useRef ( callback ) ; const mounted = useRef ( true ) ; funcRef . current = callback ; if ( typeof callback !== 'function' ) { throw new TypeError ( 'Expected a function' ) ; } wait = + wait || 0 ; options = options || { } ; const leading = ! ! options . leading ; const trailing = 'trailing' in options ? ! ! options . trailing : true ; const maxing = 'maxWait' in options ; const maxWait = maxing ? Math . max ( + options . maxWait || 0 , wait ) : null ; useEffect ( ( ) => { mounted . current = true ; return ( ) => { mounted . current = false ; } ; } , [ ] ) ; const debounced = useMemo ( ( ) => { const invokeFunc = ( time ) => { const args = lastArgs . current ; const thisArg = lastThis . current ; lastArgs . current = lastThis . current = null ; lastInvokeTime . current = time ; return ( result . current = funcRef . current . apply ( thisArg , args ) ) ; } ; const startTimer = ( pendingFunc , timerWait ) => { timerId . current = setTimeout ( pendingFunc , timerWait ) ; } ; const shouldInvoke = ( time ) => { if ( ! mounted . current ) { return false ; } const timeSinceLastCall = time - lastCallTime . current ; const timeSinceLastInvoke = time - lastInvokeTime . current ; return ( ! lastCallTime . current || timeSinceLastCall >= wait || timeSinceLastCall < 0 || ( maxing && timeSinceLastInvoke >= maxWait ) ) ; } ; const trailingEdge = ( time ) => { timerId . current = null ; if ( trailing && lastArgs . current ) { return invokeFunc ( time ) ; } lastArgs . current = lastThis . current = null ; return result . current ; } ; const timerExpired = ( ) => { const time = Date . now ( ) ; if ( shouldInvoke ( time ) ) { return trailingEdge ( time ) ; } if ( ! mounted . current ) { return ; } const timeSinceLastCall = time - lastCallTime . current ; const timeSinceLastInvoke = time - lastInvokeTime . current ; const timeWaiting = wait - timeSinceLastCall ; const remainingWait = maxing ? Math . min ( timeWaiting , maxWait - timeSinceLastInvoke ) : timeWaiting ; startTimer ( timerExpired , remainingWait ) ; } ; const func = ( ... args ) : ReturnType < T > => { const time = Date . now ( ) ; const isInvoking = shouldInvoke ( time ) ; lastArgs . current = args ; lastThis . current = this ; lastCallTime . current = time ; if ( isInvoking ) { if ( ! timerId . current && mounted . current ) { lastInvokeTime . current = lastCallTime . current ; startTimer ( timerExpired , wait ) ; return leading ? invokeFunc ( lastCallTime . current ) : result . current ; } if ( maxing ) { startTimer ( timerExpired , wait ) ; return invokeFunc ( lastCallTime . current ) ; } } if ( ! timerId . current ) { startTimer ( timerExpired , wait ) ; } return result . current ; } ; func . cancel = ( ) => { if ( timerId . current ) { clearTimeout ( timerId . current ) ; } lastInvokeTime . current = 0 ; lastArgs . current = lastCallTime . current = lastThis . current = timerId . current = null ; } ; func . isPending = ( ) => { return ! ! timerId . current ; } ; func . flush = ( ) => { return ! timerId . current ? result . current : trailingEdge ( Date . now ( ) ) ; } ; return func ; } , [ leading , maxing , wait , maxWait , trailing ] ) ; return debounced ; }