Closed4
[React] View Transitions API を使ったスライドショー
カスタムフック
import { useEffect, useState } from "react";
import { flushSync } from "react-dom";
export const useSlideshow = (query: string, interval: number) => {
const [url, setUrl] = useState(query);
useEffect(() => {
const timerId = setInterval(() => {
fetch(query)
.then((res) => {
if (document.startViewTransition) {
document.startViewTransition(() => {
flushSync(() => {
setUrl(res.url);
});
});
} else {
flushSync(() => {
setUrl(res.url);
});
}
})
.catch((err) => console.error(`${err}`));
}, interval);
return () => {
clearInterval(timerId);
};
}, [query, interval]);
return [url, setUrl] as const;
};
型宣言
export interface ViewTransition {
ready: Promise<void>;
finished: Promise<void>;
updateCallbackDone: Promise<void>;
skipTransition: () => undefined;
}
declare global {
interface Document {
startViewTransition?: (callback: () => void) => ViewTransition;
}
}
テストファイル
import "@testing-library/jest-dom";
import { act, render, screen } from "@testing-library/react";
import { useSlideshow } from "../useSlideshow";
vi.useFakeTimers();
const query = "https://unsplash.com/photos/bIhpiQA009k";
describe("useSlideshow", () => {
beforeEach(() => {
vi.clearAllTimers();
vi.clearAllMocks();
delete document.startViewTransition;
});
afterEach(() => {
delete document.startViewTransition;
});
it("should update the url after specified interval without transition", async () => {
const mockFetch = Promise.resolve({
url: query,
});
global.fetch = vi.fn().mockResolvedValueOnce(mockFetch);
function MockComponent() {
const [url] = useSlideshow(query, 1000);
return <div>{url}</div>;
}
render(<MockComponent />);
await act(async () => {
vi.advanceTimersByTime(1000);
});
expect(screen.getByText(query)).toBeInTheDocument();
expect(document.startViewTransition).toBeUndefined();
});
it("should update the url after specified interval with transition", async () => {
document.startViewTransition = vi.fn().mockImplementationOnce((cb) => {
cb();
});
const mockFetch = Promise.resolve({
url: query,
});
global.fetch = vi.fn().mockResolvedValueOnce(mockFetch);
function MockComponent() {
const [url] = useSlideshow(query, 1000);
return <div>{url}</div>;
}
render(<MockComponent />);
await act(async () => {
vi.advanceTimersByTime(1000);
});
expect(screen.getByText(query)).toBeInTheDocument();
expect(document.startViewTransition).toHaveBeenCalledTimes(1);
});
it("should catch error and log it to the console", async () => {
const consoleSpy = vi.spyOn(console, "error");
global.fetch = vi.fn().mockRejectedValueOnce(new Error("Fetch error"));
function MockComponent() {
const [url] = useSlideshow(query, 1000);
return <div>{url}</div>;
}
render(<MockComponent />);
await act(async () => {
vi.advanceTimersByTime(1000);
});
expect(consoleSpy).toHaveBeenCalledWith("Error: Fetch error");
consoleSpy.mockRestore();
});
});
このスクラップは2023/04/07にクローズされました