UNPKG

use-debounce

Version:
1 lines 18.9 kB
{"version":3,"file":"index.mjs","sources":["../src/useDebouncedCallback.ts","../src/useDebounce.ts","../src/useThrottledCallback.ts"],"sourcesContent":["import {\n useRef,\n useEffect,\n useMemo,\n type Dispatch,\n type SetStateAction,\n} from 'react';\n\nexport interface CallOptions {\n /**\n * Controls if the function should be invoked on the leading edge of the timeout.\n */\n leading?: boolean;\n /**\n * Controls if the function should be invoked on the trailing edge of the timeout.\n */\n trailing?: boolean;\n}\n\nexport interface Options extends CallOptions {\n /**\n * The maximum time the given function is allowed to be delayed before it's invoked.\n */\n maxWait?: number;\n /**\n * If the setting is set to true, all debouncing and timers will happen on the server side as well\n */\n debounceOnServer?: boolean;\n}\n\nexport interface ControlFunctions<ReturnT> {\n /**\n * Cancel pending function invocations\n */\n cancel: () => void;\n /**\n * Immediately invoke pending function invocations\n */\n flush: () => ReturnT | undefined;\n /**\n * Returns `true` if there are any pending function invocations\n */\n isPending: () => boolean;\n}\n\n/**\n * Subsequent calls to the debounced function return the result of the last func invocation.\n * Note, that if there are no previous invocations you will get undefined. You should check it in your code properly.\n */\nexport interface DebouncedState<T extends (...args: any) => ReturnType<T>>\n extends ControlFunctions<ReturnType<T>> {\n (...args: Parameters<T>): ReturnType<T> | undefined;\n}\n\n/**\n * Creates a debounced function that delays invoking `func` until after `wait`\n * milliseconds have elapsed since the last time the debounced function was\n * invoked, or until the next browser frame is drawn.\n *\n * The debounced function comes with a `cancel` method to cancel delayed `func`\n * invocations and a `flush` method to immediately invoke them.\n *\n * Provide `options` to indicate whether `func` should be invoked on the leading\n * and/or trailing edge of the `wait` timeout. The `func` is invoked with the\n * last arguments provided to the debounced function.\n *\n * Subsequent calls to the debounced function return the result of the last\n * `func` invocation.\n *\n * **Note:** If `leading` and `trailing` options are `true`, `func` is\n * invoked on the trailing edge of the timeout only if the debounced function\n * is invoked more than once during the `wait` timeout.\n *\n * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred\n * until the next tick, similar to `setTimeout` with a timeout of `0`.\n *\n * If `wait` is omitted in an environment with `requestAnimationFrame`, `func`\n * invocation will be deferred until the next frame is drawn (typically about\n * 16ms).\n *\n * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)\n * for details over the differences between `debounce` and `throttle`.\n *\n * @category Function\n * @param {Function} func The function to debounce.\n * @param {number} [wait=0]\n * The number of milliseconds to delay; if omitted, `requestAnimationFrame` is\n * used (if available, otherwise it will be setTimeout(...,0)).\n * @param {Object} [options={}] The options object.\n * Controls if `func` should be invoked on the leading edge of the timeout.\n * @param {boolean} [options.leading=false]\n * The maximum time `func` is allowed to be delayed before it's invoked.\n * @param {number} [options.maxWait]\n * Controls if `func` should be invoked the trailing edge of the timeout.\n * @param {boolean} [options.trailing=true]\n * @returns {Function} Returns the new debounced function.\n * @example\n *\n * // Avoid costly calculations while the window size is in flux.\n * const resizeHandler = useDebouncedCallback(calculateLayout, 150);\n * window.addEventListener('resize', resizeHandler)\n *\n * // Invoke `sendMail` when clicked, debouncing subsequent calls.\n * const clickHandler = useDebouncedCallback(sendMail, 300, {\n * leading: true,\n * trailing: false,\n * })\n * <button onClick={clickHandler}>click me</button>\n *\n * // Ensure `batchLog` is invoked once after 1 second of debounced calls.\n * const debounced = useDebouncedCallback(batchLog, 250, { 'maxWait': 1000 })\n * const source = new EventSource('/stream')\n * source.addEventListener('message', debounced)\n *\n * // Cancel the trailing debounced invocation.\n * window.addEventListener('popstate', debounced.cancel)\n *\n * // Check for pending invocations.\n * const status = debounced.isPending() ? \"Pending...\" : \"Ready\"\n */\nexport default function useDebouncedCallback<\n T extends (...args: any) => ReturnType<T>,\n>(\n func: T,\n wait?: number,\n options?: Options,\n forceUpdate?: Dispatch<SetStateAction<object>>\n): DebouncedState<T> {\n const lastCallTime = useRef(null);\n const lastInvokeTime = useRef(0);\n const firstInvokeTime = useRef(0);\n const timerId = useRef(null);\n const lastArgs = useRef<unknown[]>([]);\n const lastThis = useRef<unknown>();\n const result = useRef<ReturnType<T>>();\n const funcRef = useRef(func);\n const mounted = useRef(true);\n // Always keep the latest version of debounce callback, with no wait time.\n funcRef.current = func;\n\n const isClientSide = typeof window !== 'undefined';\n // Bypass `requestAnimationFrame` by explicitly setting `wait=0`.\n const useRAF = !wait && wait !== 0 && isClientSide;\n\n if (typeof func !== 'function') {\n throw new TypeError('Expected a function');\n }\n\n wait = +wait || 0;\n options = options || {};\n\n const leading = !!options.leading;\n const trailing = 'trailing' in options ? !!options.trailing : true; // `true` by default\n const maxing = 'maxWait' in options;\n const debounceOnServer =\n 'debounceOnServer' in options ? !!options.debounceOnServer : false; // `false` by default\n const maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : null;\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n\n // You may have a question, why we have so many code under the useMemo definition.\n //\n // This was made as we want to escape from useCallback hell and\n // not to initialize a number of functions each time useDebouncedCallback is called.\n //\n // It means that we have less garbage for our GC calls which improves performance.\n // Also, it makes this library smaller.\n //\n // And the last reason, that the code without lots of useCallback with deps is easier to read.\n // You have only one place for that.\n const debounced = useMemo(() => {\n const invokeFunc = (time: number) => {\n const args = lastArgs.current;\n const thisArg = lastThis.current;\n lastArgs.current = lastThis.current = null;\n lastInvokeTime.current = time;\n firstInvokeTime.current = firstInvokeTime.current || time;\n\n return (result.current = funcRef.current.apply(thisArg, args));\n };\n\n const startTimer = (pendingFunc: () => void, wait: number) => {\n if (useRAF) cancelAnimationFrame(timerId.current);\n timerId.current = useRAF\n ? requestAnimationFrame(pendingFunc)\n : setTimeout(pendingFunc, wait);\n };\n\n const shouldInvoke = (time: number) => {\n if (!mounted.current) return false;\n\n const timeSinceLastCall = time - lastCallTime.current;\n const timeSinceLastInvoke = time - lastInvokeTime.current;\n\n // Either this is the first call, activity has stopped and we're at the\n // trailing edge, the system time has gone backwards and we're treating\n // it as the trailing edge, or we've hit the `maxWait` limit.\n return (\n !lastCallTime.current ||\n timeSinceLastCall >= wait ||\n timeSinceLastCall < 0 ||\n (maxing && timeSinceLastInvoke >= maxWait)\n );\n };\n\n const trailingEdge = (time: number) => {\n timerId.current = null;\n\n // Only invoke if we have `lastArgs` which means `func` has been\n // debounced at least once.\n if (trailing && lastArgs.current) {\n return invokeFunc(time);\n }\n\n lastArgs.current = lastThis.current = null;\n return result.current;\n };\n\n const timerExpired = () => {\n const time = Date.now();\n\n if (leading && firstInvokeTime.current === lastInvokeTime.current) {\n notifyManuallyTimerExpired();\n }\n\n if (shouldInvoke(time)) {\n return trailingEdge(time);\n }\n // https://github.com/xnimorz/use-debounce/issues/97\n if (!mounted.current) {\n return;\n }\n // Remaining wait calculation\n const timeSinceLastCall = time - lastCallTime.current;\n const timeSinceLastInvoke = time - lastInvokeTime.current;\n const timeWaiting = wait - timeSinceLastCall;\n const remainingWait = maxing\n ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)\n : timeWaiting;\n\n // Restart the timer\n startTimer(timerExpired, remainingWait);\n };\n\n const notifyManuallyTimerExpired = () => {\n if (forceUpdate) {\n forceUpdate({});\n }\n };\n\n const func: DebouncedState<T> = (...args: Parameters<T>): ReturnType<T> => {\n if (!isClientSide && !debounceOnServer) {\n return;\n }\n const time = Date.now();\n const isInvoking = shouldInvoke(time);\n\n lastArgs.current = args;\n lastThis.current = this;\n lastCallTime.current = time;\n\n if (isInvoking) {\n if (!timerId.current && mounted.current) {\n // Reset any `maxWait` timer.\n lastInvokeTime.current = lastCallTime.current;\n // Start the timer for the trailing edge.\n startTimer(timerExpired, wait);\n // Invoke the leading edge.\n return leading ? invokeFunc(lastCallTime.current) : result.current;\n }\n if (maxing) {\n // Handle invocations in a tight loop.\n startTimer(timerExpired, wait);\n return invokeFunc(lastCallTime.current);\n }\n }\n if (!timerId.current) {\n startTimer(timerExpired, wait);\n }\n return result.current;\n };\n\n func.cancel = () => {\n const hadTimer = timerId.current;\n if (hadTimer) {\n useRAF\n ? cancelAnimationFrame(timerId.current)\n : clearTimeout(timerId.current);\n }\n lastInvokeTime.current = 0;\n lastArgs.current =\n lastCallTime.current =\n lastThis.current =\n timerId.current =\n null;\n\n // Notify React to re-render when cancel is called and there was an active timer\n if (hadTimer && forceUpdate) {\n forceUpdate({});\n }\n };\n\n func.isPending = () => {\n return !!timerId.current;\n };\n\n func.flush = () => {\n return !timerId.current ? result.current : trailingEdge(Date.now());\n };\n\n return func;\n }, [\n leading,\n maxing,\n wait,\n maxWait,\n trailing,\n useRAF,\n isClientSide,\n debounceOnServer,\n forceUpdate,\n ]);\n\n return debounced;\n}\n","import { useCallback, useRef, useState } from 'react';\nimport useDebouncedCallback, { DebouncedState } from './useDebouncedCallback';\n\nfunction valueEquality<T>(left: T, right: T): boolean {\n return left === right;\n}\n\nexport default function useDebounce<T>(\n value: T,\n delay: number,\n options?: {\n maxWait?: number;\n leading?: boolean;\n trailing?: boolean;\n equalityFn?: (left: T, right: T) => boolean;\n }\n): [T, DebouncedState<(value: T) => void>] {\n const eq = (options && options.equalityFn) || valueEquality;\n\n const activeValue = useRef(value);\n const [, forceUpdate] = useState({});\n const debounced = useDebouncedCallback(\n useCallback(\n (value: T) => {\n activeValue.current = value;\n forceUpdate({});\n },\n [forceUpdate]\n ),\n delay,\n options,\n forceUpdate\n );\n const previousValue = useRef(value);\n\n if (!eq(previousValue.current, value)) {\n debounced(value);\n previousValue.current = value;\n }\n\n return [activeValue.current as T, debounced];\n}\n","import useDebouncedCallback, {\n CallOptions,\n DebouncedState,\n} from './useDebouncedCallback';\n\n/**\n * Creates a throttled function that only invokes `func` at most once per\n * every `wait` milliseconds (or once per browser frame).\n *\n * The throttled function comes with a `cancel` method to cancel delayed `func`\n * invocations and a `flush` method to immediately invoke them.\n *\n * Provide `options` to indicate whether `func` should be invoked on the leading\n * and/or trailing edge of the `wait` timeout. The `func` is invoked with the\n * last arguments provided to the throttled function.\n *\n * Subsequent calls to the throttled function return the result of the last\n * `func` invocation.\n *\n * **Note:** If `leading` and `trailing` options are `true`, `func` is\n * invoked on the trailing edge of the timeout only if the throttled function\n * is invoked more than once during the `wait` timeout.\n *\n * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred\n * until the next tick, similar to `setTimeout` with a timeout of `0`.\n *\n * If `wait` is omitted in an environment with `requestAnimationFrame`, `func`\n * invocation will be deferred until the next frame is drawn (typically about\n * 16ms).\n *\n * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)\n * for details over the differences between `throttle` and `debounce`.\n *\n * @category Function\n * @param {Function} func The function to throttle.\n * @param {number} [wait=0]\n * The number of milliseconds to throttle invocations to; if omitted,\n * `requestAnimationFrame` is used (if available, otherwise it will be setTimeout(...,0)).\n * @param {Object} [options={}] The options object.\n * @param {boolean} [options.leading=true]\n * Specify invoking on the leading edge of the timeout.\n * @param {boolean} [options.trailing=true]\n * Specify invoking on the trailing edge of the timeout.\n * @returns {Function} Returns the new throttled function.\n * @example\n *\n * // Avoid excessively updating the position while scrolling.\n * const scrollHandler = useThrottledCallback(updatePosition, 100)\n * window.addEventListener('scroll', scrollHandler)\n *\n * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.\n * const throttled = useThrottledCallback(renewToken, 300000, { 'trailing': false })\n * <button onClick={throttled}>click</button>\n *\n * // Cancel the trailing throttled invocation.\n * window.addEventListener('popstate', throttled.cancel);\n */\nexport default function useThrottledCallback<\n T extends (...args: any) => ReturnType<T>,\n>(\n func: T,\n wait: number,\n { leading = true, trailing = true }: CallOptions = {}\n): DebouncedState<T> {\n return useDebouncedCallback(func, wait, {\n maxWait: wait,\n leading,\n trailing,\n });\n}\n"],"names":["useDebouncedCallback","func","wait","options","forceUpdate","lastCallTime","useRef","lastInvokeTime","firstInvokeTime","timerId","lastArgs","lastThis","result","funcRef","mounted","current","isClientSide","window","useRAF","TypeError","leading","trailing","maxing","debounceOnServer","maxWait","Math","max","useEffect","debounced","useMemo","invokeFunc","time","args","thisArg","apply","startTimer","pendingFunc","cancelAnimationFrame","requestAnimationFrame","setTimeout","shouldInvoke","timeSinceLastCall","trailingEdge","timerExpired","Date","now","notifyManuallyTimerExpired","timeWaiting","remainingWait","min","isInvoking","this","cancel","hadTimer","clearTimeout","isPending","flush","valueEquality","left","right","useDebounce","value","delay","eq","equalityFn","activeValue","useState","useCallback","previousValue","useThrottledCallback"],"mappings":"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"}