From 9887b10bdf403e85eb3d7402c0ba2d1ba63c35eb Mon Sep 17 00:00:00 2001 From: Wyatt Bai <53634020+congweibai@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:17:26 +0930 Subject: [PATCH] feat: useTimeout hook --- src/hooks/index.ts | 1 + src/hooks/useTimeout/index.ts | 1 + src/hooks/useTimeout/useTimeout.test.tsx | 97 ++++++++++++++++++++++++ src/hooks/useTimeout/useTimeout.tsx | 14 ++++ 4 files changed, 113 insertions(+) create mode 100644 src/hooks/useTimeout/index.ts create mode 100644 src/hooks/useTimeout/useTimeout.test.tsx create mode 100644 src/hooks/useTimeout/useTimeout.tsx diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 82c467f..d6f382d 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -7,3 +7,4 @@ export * from "./useCounter/useCounter"; export * from "./useHover/useHover"; export * from "./useFavicon/useFavicon"; export * from "./useQueue/useQueue"; +export * from "./useTimeout"; diff --git a/src/hooks/useTimeout/index.ts b/src/hooks/useTimeout/index.ts new file mode 100644 index 0000000..949d716 --- /dev/null +++ b/src/hooks/useTimeout/index.ts @@ -0,0 +1 @@ +export * from "./useTimeout"; diff --git a/src/hooks/useTimeout/useTimeout.test.tsx b/src/hooks/useTimeout/useTimeout.test.tsx new file mode 100644 index 0000000..aaf89b1 --- /dev/null +++ b/src/hooks/useTimeout/useTimeout.test.tsx @@ -0,0 +1,97 @@ +import { describe, expect, it, beforeEach, vi, afterEach } from "vitest"; +import useTimeout from "./useTimeout"; +import { act } from "react-dom/test-utils"; +import { render } from "@testing-library/react"; + +// Test Component using the useTimeout hook +function TestComponent({ + onTimeout, + delay, +}: { + onTimeout: () => void; + delay: number; +}) { + useTimeout(onTimeout, delay); + return null; +} + +describe("useTimeout", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it("should call the callback after the specified timeout", () => { + // mock + const callback = vi.fn(); + const delay = 1000; + + // act + render(); + + act(() => { + vi.advanceTimersByTime(delay); + }); + + // assert + expect(callback).toHaveBeenCalledTimes(1); + }); + + it("should clear timeout on unmount", () => { + // mock + const callback = vi.fn(); + const delay = 1000; + + // act + const { unmount } = render( + + ); + + act(() => { + vi.advanceTimersByTime(delay / 2); + }); + + unmount(); + + act(() => { + vi.advanceTimersByTime(delay / 2); + }); + + // assert + expect(callback).not.toHaveBeenCalled(); + }); + + it("should clear the timeout when delay changes", () => { + // mock + const callback = vi.fn(); + const initialDelay = 1000; + const newDelay = 2000; + + // act + const { rerender } = render( + + ); + + act(() => { + vi.advanceTimersByTime(initialDelay / 2); + }); + + rerender(); + + act(() => { + vi.advanceTimersByTime(newDelay / 2); + }); + + // assert + expect(callback).not.toHaveBeenCalled(); + + act(() => { + vi.advanceTimersByTime(newDelay / 2); + }); + + expect(callback).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/hooks/useTimeout/useTimeout.tsx b/src/hooks/useTimeout/useTimeout.tsx new file mode 100644 index 0000000..9662cd6 --- /dev/null +++ b/src/hooks/useTimeout/useTimeout.tsx @@ -0,0 +1,14 @@ +import { useEffect, useRef, useCallback } from "react"; + +export default function useTimeout(cb: () => void, ms: number) { + const timeId = useRef(null); + const clearCurrentTimeOut = useCallback(() => { + if (timeId.current) clearTimeout(timeId.current); + }, []); + + useEffect(() => { + timeId.current = setTimeout(cb, ms); + return clearCurrentTimeOut; + }, [ms, cb]); + return clearCurrentTimeOut; +}