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;
+}