Last active
March 16, 2020 08:22
-
-
Save astoilkov/a5a6e6c6d11a48fe049ee4d25230653f to your computer and use it in GitHub Desktop.
Revisions
-
astoilkov revised this gist
Mar 14, 2020 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -14,11 +14,11 @@ import { useCallback, useLayoutEffect, DependencyList } from 'react' * That doesn't apply for state and props because they are bound to the current * function context. */ export default function useDebounceCallback<T extends (...args: any[]) => void>( callback: T, delay: number, deps: DependencyList, ): T { let disposed = false let timeoutId: number | undefined let callbackWrapper: Function | undefined -
astoilkov revised this gist
Mar 14, 2020 . No changes.There are no files selected for viewing
-
astoilkov created this gist
Mar 14, 2020 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,79 @@ import { useCallback, useLayoutEffect, DependencyList } from 'react' /** * This version bounces each version of the callback. This ensures that the callback * will be called with each state of the application. That's why `deps` is a required argument. * * Previous version used `useRef` for `timeoutId` and didn't have `deps` argument. * This resulted in missing calling the callback for the previous state and necessarily * calling it for the new state. * * Note: In theory bounced callbacks shouldn't access `Ref` instances inside of the * callback. The reason is that `Ref` instance values can change between the time * the callback is executed and the time the bouncing decides to execute it. * That doesn't apply for state and props because they are bound to the current * function context. */ export default <T extends (...args: any[]) => void>( callback: T, delay: number, deps: DependencyList, ): T => { let disposed = false let timeoutId: number | undefined let callbackWrapper: Function | undefined /** * Ensures the callback is called immediately after `deps` change * instead of waiting for the `setTimeout` to fire. * * Using `useLayoutEffect` instead of `useEffect` because otherwise the * callback will be called after all `useLayoutEffect` are executed. * `useLayoutEffect` hooks can change `Ref` values and thus change the * `Ref` values accessed in the bounced callback. */ useLayoutEffect(() => { return () => { /** * Disabling `react-hooks/exhaustive-deps` because we intentionally want to not use refs for * the `disposed` property: * Assignments to the 'disposed' variable from inside React Hook useLayoutEffect will be lost * after each render. To preserve the value over time, store it in a useRef Hook and keep the * mutable value in the '.current' property. Otherwise, you can move this variable directly * inside useLayoutEffect. */ // eslint-disable-next-line react-hooks/exhaustive-deps disposed = true if (callbackWrapper === undefined || timeoutId === undefined) { return } callbackWrapper() clearTimeout(timeoutId) } }, deps) return useCallback<T>( function useDebounceCallback(...args) { if (disposed) { throw new Error( [ 'Trying to call an already disposed callback.', 'In theory you should never call a disposed callback.', 'This is probably a bug.', ].join(' '), ) } clearTimeout(timeoutId) callbackWrapper = () => { timeoutId = undefined return callback(...args) } timeoutId = window.setTimeout(callbackWrapper, delay) } as T, deps, ) }