Add 'useSetTimeout' hook to manage timers (#2134)
This commit is contained in:
parent
79827249ed
commit
06de428848
3 changed files with 97 additions and 0 deletions
|
@ -4,6 +4,7 @@ import { AlertVariant } from "@patternfly/react-core";
|
||||||
import type { AxiosError } from "axios";
|
import type { AxiosError } from "axios";
|
||||||
|
|
||||||
import useRequiredContext from "../../utils/useRequiredContext";
|
import useRequiredContext from "../../utils/useRequiredContext";
|
||||||
|
import useSetTimeout from "../../utils/useSetTimeout";
|
||||||
import { AlertPanel, AlertType } from "./AlertPanel";
|
import { AlertPanel, AlertType } from "./AlertPanel";
|
||||||
|
|
||||||
type AlertProps = {
|
type AlertProps = {
|
||||||
|
@ -23,6 +24,7 @@ export const useAlerts = () => useRequiredContext(AlertContext);
|
||||||
export const AlertProvider: FunctionComponent = ({ children }) => {
|
export const AlertProvider: FunctionComponent = ({ children }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [alerts, setAlerts] = useState<AlertType[]>([]);
|
const [alerts, setAlerts] = useState<AlertType[]>([]);
|
||||||
|
const setTimeout = useSetTimeout();
|
||||||
|
|
||||||
const createId = () => new Date().getTime();
|
const createId = () => new Date().getTime();
|
||||||
|
|
||||||
|
|
61
src/utils/useSetTimeout.test.ts
Normal file
61
src/utils/useSetTimeout.test.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* @jest-environment jsdom
|
||||||
|
*/
|
||||||
|
import { renderHook } from "@testing-library/react-hooks";
|
||||||
|
import useSetTimeout from "./useSetTimeout";
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
describe("useSetTimeout", () => {
|
||||||
|
it("schedules timeouts and triggers the callbacks", () => {
|
||||||
|
const { result } = renderHook(() => useSetTimeout());
|
||||||
|
const setTimeoutSpy = jest.spyOn(global, "setTimeout");
|
||||||
|
|
||||||
|
// Schedule some timeouts...
|
||||||
|
const callback1 = jest.fn();
|
||||||
|
const callback2 = jest.fn();
|
||||||
|
result.current(callback1, 1000);
|
||||||
|
result.current(callback2, 500);
|
||||||
|
|
||||||
|
// Ensure that setTimeout was actually called with the correct arguments.
|
||||||
|
expect(setTimeoutSpy).toHaveBeenCalledTimes(2);
|
||||||
|
expect(setTimeoutSpy).toBeCalledWith(expect.any(Function), 1000);
|
||||||
|
expect(setTimeoutSpy).toBeCalledWith(expect.any(Function), 500);
|
||||||
|
|
||||||
|
// Ensure callbacks are called after timers run.
|
||||||
|
expect(callback2).not.toBeCalled();
|
||||||
|
jest.advanceTimersByTime(500);
|
||||||
|
expect(callback1).not.toBeCalled();
|
||||||
|
expect(callback2).toBeCalled();
|
||||||
|
jest.advanceTimersByTime(500);
|
||||||
|
expect(callback1).toBeCalled();
|
||||||
|
|
||||||
|
setTimeoutSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws if a timeout is scheduled after the component has unmounted", () => {
|
||||||
|
const { result, unmount } = renderHook(() => useSetTimeout());
|
||||||
|
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
expect(() => result.current(jest.fn(), 1000)).toThrowError(
|
||||||
|
"Can't schedule a timeout on an unmounted component."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clears a timeout if the component unmounts", () => {
|
||||||
|
const { result, unmount } = renderHook(() => useSetTimeout());
|
||||||
|
const timerId = 42;
|
||||||
|
const setTimeoutSpy = jest
|
||||||
|
.spyOn(global, "setTimeout")
|
||||||
|
.mockReturnValueOnce(timerId as unknown as NodeJS.Timeout);
|
||||||
|
const clearTimeoutSpy = jest.spyOn(global, "clearTimeout");
|
||||||
|
|
||||||
|
result.current(jest.fn(), 1000);
|
||||||
|
unmount();
|
||||||
|
|
||||||
|
expect(clearTimeoutSpy).toBeCalledWith(timerId);
|
||||||
|
|
||||||
|
setTimeoutSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
34
src/utils/useSetTimeout.ts
Normal file
34
src/utils/useSetTimeout.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export default function useSetTimeout() {
|
||||||
|
const didUnmountRef = useRef(false);
|
||||||
|
const { current: scheduledTimers } = useRef(new Set<number>());
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
didUnmountRef.current = true;
|
||||||
|
clearAll();
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
function clearAll() {
|
||||||
|
scheduledTimers.forEach((timer) => clearTimeout(timer));
|
||||||
|
scheduledTimers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return function scheduleTimeout(callback: () => void, delay: number) {
|
||||||
|
if (didUnmountRef.current) {
|
||||||
|
throw new Error("Can't schedule a timeout on an unmounted component.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const timer = Number(setTimeout(handleCallback, delay));
|
||||||
|
|
||||||
|
scheduledTimers.add(timer);
|
||||||
|
|
||||||
|
function handleCallback() {
|
||||||
|
scheduledTimers.delete(timer);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue