/** * A React hook that follows one generation of this site's generation API * until it ends, with the client of open-api-client.ts (put both files side * by side). The polling is the client's `wait`: 5 seconds apart at first, * then up to 10, longer when the API asks; it stops when the component * unmounts or follows another id. With `timeoutMs`, as the client's `wait`, * it stops waiting after about that long, with the error; a generation read * as ended is shown as the result, never dropped for the time. * * const { generation, error, done } = useGeneration(client, id); * * In a browser, use a client without an API key (the visitor's session on * this site): a key never belongs in a browser. */ import { useEffect, useRef, useState } from 'react'; import type { Generation, GenerationClient } from './open-api-client'; export type GenerationState = { /** The latest reading, null before the first. */ generation: Generation | null; /** Why following it stopped early (an OpenApiError, mostly). */ error: unknown; /** It ended, or following it failed. */ done: boolean; }; const IDLE: GenerationState = { generation: null, error: null, done: false }; export function useGeneration( client: Pick, id: string | null, options: { timeoutMs?: number } = {} ): GenerationState { const [state, setState] = useState(IDLE); // The latest client, without starting over when a new one comes each render. const latest = useRef(client); latest.current = client; const { timeoutMs } = options; useEffect(() => { setState(IDLE); if (!id) return; const controller = new AbortController(); const live = () => !controller.signal.aborted; latest.current .wait(id, { signal: controller.signal, timeoutMs, onUpdate: (generation) => { if (live()) setState({ generation, error: null, done: false }); }, }) .then( (generation) => { if (live()) setState({ generation, error: null, done: true }); }, (error: unknown) => { if (live()) setState((current) => ({ generation: current.generation, error, done: true, })); } ); return () => controller.abort(); }, [id, timeoutMs]); return state; }