Chore: full react hooks eslint compliance (#7040)

This commit is contained in:
shamoon
2026-08-21 14:00:02 -07:00
committed by GitHub
parent 7c26cf6d97
commit 4c361422ca
111 changed files with 1072 additions and 867 deletions
+45
View File
@@ -0,0 +1,45 @@
import { useSyncExternalStore } from "react";
const MINUTE = 60_000;
const stores = new Map();
function getStore(refreshInterval) {
const existing = stores.get(refreshInterval);
if (existing) return existing;
const listeners = new Set();
let now = null; // null until mounted, so SSR and hydration render the same thing
let timer = null;
const store = {
getSnapshot: () => now,
subscribe: (onStoreChange) => {
listeners.add(onStoreChange);
if (!timer) {
now = Date.now();
timer = setInterval(() => {
now = Date.now();
listeners.forEach((listener) => listener());
}, refreshInterval);
}
return () => {
listeners.delete(onStoreChange);
if (!listeners.size) {
clearInterval(timer);
timer = null;
}
};
},
};
stores.set(refreshInterval, store);
return store;
}
// Returns null on the server and during hydration, then the current time.
export default function useCurrentTime(refreshInterval = MINUTE) {
const { subscribe, getSnapshot } = getStore(refreshInterval);
return useSyncExternalStore(subscribe, getSnapshot, () => null);
}
+32
View File
@@ -0,0 +1,32 @@
// @vitest-environment jsdom
import { act, render, screen } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import useCurrentTime from "./use-current-time";
function CurrentTime({ refreshInterval }) {
return <span>{useCurrentTime(refreshInterval)}</span>;
}
describe("utils/hooks/use-current-time", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(1_000);
});
afterEach(() => {
vi.useRealTimers();
});
it("updates at the requested interval", () => {
render(<CurrentTime refreshInterval={5_000} />);
expect(screen.getByText("1000")).toBeInTheDocument();
act(() => {
vi.advanceTimersByTime(5_000);
});
expect(screen.getByText("6000")).toBeInTheDocument();
});
});
+10 -19
View File
@@ -1,26 +1,17 @@
import { useEffect, useState } from "react";
import { useSyncExternalStore } from "react";
const hasFocus = () => typeof document !== "undefined" && document.hasFocus();
const useWindowFocus = () => {
const [focused, setFocused] = useState(hasFocus);
const subscribe = (onFocusChange) => {
window.addEventListener("focus", onFocusChange);
window.addEventListener("blur", onFocusChange);
useEffect(() => {
setFocused(hasFocus());
const onFocus = () => setFocused(true);
const onBlur = () => setFocused(false);
window.addEventListener("focus", onFocus);
window.addEventListener("blur", onBlur);
return () => {
window.removeEventListener("focus", onFocus);
window.removeEventListener("blur", onBlur);
};
}, []);
return focused;
return () => {
window.removeEventListener("focus", onFocusChange);
window.removeEventListener("blur", onFocusChange);
};
};
const useWindowFocus = () => useSyncExternalStore(subscribe, hasFocus, () => false);
export default useWindowFocus;
+3 -1
View File
@@ -12,15 +12,17 @@ function Fixture() {
describe("utils/hooks/window-focus", () => {
it("tracks focus/blur events", async () => {
vi.spyOn(document, "hasFocus").mockReturnValue(true);
const hasFocus = vi.spyOn(document, "hasFocus").mockReturnValue(true);
render(<Fixture />);
expect(screen.getByTestId("focused")).toHaveTextContent("true");
hasFocus.mockReturnValue(false);
window.dispatchEvent(new Event("blur"));
await waitFor(() => expect(screen.getByTestId("focused")).toHaveTextContent("false"));
hasFocus.mockReturnValue(true);
window.dispatchEvent(new Event("focus"));
await waitFor(() => expect(screen.getByTestId("focused")).toHaveTextContent("true"));
});