import { useEffect, useState } from 'react'; /** * The current time, as state that advances on an interval. * * Returns a value rather than merely forcing a re-render, and that is the point. * A component that calls `Date.now()` while rendering produces output that * depends on the clock, which React is entitled to assume it does not — the * `react-hooks/purity` rule says so, and it was the only instance in this * codebase precisely because it was the only place doing it. Reading `now` from * state makes render a function of its inputs again. * * It also fixes the defect underneath that rule. Nothing scheduled a re-render, * so the cart's countdown was a still photograph of the moment the page loaded, * and the red warning in the final stretch could only appear by accident — the * component had already rendered before the final stretch began. * * `active` exists so an idle page is not waking React forever: an empty cart has * nothing to count down, so it does not tick at all. */ export function useNow(intervalMs: number, active: boolean): number { const [now, setNow] = useState(() => Date.now()); useEffect(() => { if (!active) return; // Set immediately as well as on the interval: mounting with `active` already // true would otherwise show a value up to intervalMs stale. // Subscribing to the clock, which is an external system — the case the // rule's own documentation names as correct. Set once here as well as on // the interval so a mount with `active` already true is not up to // intervalMs stale. // eslint-disable-next-line react-hooks/set-state-in-effect setNow(Date.now()); const timer = setInterval(() => setNow(Date.now()), intervalMs); return () => clearInterval(timer); }, [intervalMs, active]); return now; }