From 0b7a1d25d86d5c8cfa4ff62396b500fb2f41c2fd Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Thu, 15 Feb 2024 14:01:18 -0600 Subject: [PATCH 1/2] useDelay hook (#1807) --- packages/react-hooks/src/index.ts | 1 + packages/react-hooks/src/useDelay.test.ts | 55 +++++++++++++++++++++++ packages/react-hooks/src/useDelay.ts | 28 ++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 packages/react-hooks/src/useDelay.test.ts create mode 100644 packages/react-hooks/src/useDelay.ts diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index b2f6de9711..e78250303a 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -4,6 +4,7 @@ export * from './useAsyncInterval'; export * from './useCallbackWithAction'; export { default as useContextOrThrow } from './useContextOrThrow'; export * from './useDebouncedCallback'; +export * from './useDelay'; export * from './useDependentState'; export * from './useEffectNTimesWhen'; export * from './useIsEqualMemo'; diff --git a/packages/react-hooks/src/useDelay.test.ts b/packages/react-hooks/src/useDelay.test.ts new file mode 100644 index 0000000000..8ab082c6f6 --- /dev/null +++ b/packages/react-hooks/src/useDelay.test.ts @@ -0,0 +1,55 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import { useDelay } from './useDelay'; + +describe('useDelay', () => { + const delay500 = 500; + const delay1000 = 1000; + const delay2000 = 2000; + + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should return true while delay is active and false when done', () => { + const { result } = renderHook(() => useDelay(delay1000)); + expect(result.current).toBe(true); + + act(() => jest.advanceTimersByTime(delay1000)); + + expect(result.current).toBe(false); + }); + + it('should cancel setter when unmounted', () => { + const { result, unmount } = renderHook(() => useDelay(delay1000)); + unmount(); + + act(() => jest.advanceTimersByTime(delay1000)); + + expect(result.current).toBe(true); + }); + + it('should reset when delayMs is changed', () => { + const { result, rerender } = renderHook( + ({ delayMs }) => useDelay(delayMs), + { + initialProps: { delayMs: delay1000 }, + } + ); + + act(() => jest.advanceTimersByTime(delay500)); + + rerender({ delayMs: delay2000 }); + + act(() => jest.advanceTimersByTime(delay1000)); + + expect(result.current).toBe(true); + + act(() => jest.advanceTimersByTime(delay1000)); + + expect(result.current).toBe(false); + }); +}); diff --git a/packages/react-hooks/src/useDelay.ts b/packages/react-hooks/src/useDelay.ts new file mode 100644 index 0000000000..bf4d4f971a --- /dev/null +++ b/packages/react-hooks/src/useDelay.ts @@ -0,0 +1,28 @@ +import { useEffect, useState } from 'react'; + +/** + * Sets a delay, and returns a boolean indicating whether the delay is still active. + * @param delayMs The delay in milliseconds + * @returns A boolean indicating whether the delay is still active + */ +export function useDelay(delayMs: number): boolean { + const [isDelayed, setIsDelayed] = useState(true); + + useEffect(() => { + let isCancelled = false; + const timeout = setTimeout(() => { + if (!isCancelled) { + setIsDelayed(false); + } + }, delayMs); + + return () => { + isCancelled = true; + clearTimeout(timeout); + }; + }, [delayMs]); + + return isDelayed; +} + +export default useDelay; From 4dd54f0d8fc711d04e0c63639df400a47790b77f Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Thu, 15 Feb 2024 14:06:58 -0600 Subject: [PATCH 2/2] Removed isCancelled check (#1807) --- packages/react-hooks/src/useDelay.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/react-hooks/src/useDelay.ts b/packages/react-hooks/src/useDelay.ts index bf4d4f971a..d5689c957f 100644 --- a/packages/react-hooks/src/useDelay.ts +++ b/packages/react-hooks/src/useDelay.ts @@ -9,15 +9,11 @@ export function useDelay(delayMs: number): boolean { const [isDelayed, setIsDelayed] = useState(true); useEffect(() => { - let isCancelled = false; const timeout = setTimeout(() => { - if (!isCancelled) { - setIsDelayed(false); - } + setIsDelayed(false); }, delayMs); return () => { - isCancelled = true; clearTimeout(timeout); }; }, [delayMs]);