import React from 'react'; export interface Props { duration: number; expiresAt: number; onComplete?: () => unknown; } interface State { ratio: number; } const CIRCUMFERENCE = 11.013 * 2 * Math.PI; export class Countdown extends React.Component { public looping = false; constructor(props: Props) { super(props); const { duration, expiresAt } = this.props; const ratio = getRatio(expiresAt, duration); this.state = { ratio }; } public componentDidMount(): void { this.startLoop(); } public componentDidUpdate(): void { this.startLoop(); } public componentWillUnmount(): void { this.stopLoop(); } public startLoop(): void { if (this.looping) { return; } this.looping = true; requestAnimationFrame(this.loop); } public stopLoop(): void { this.looping = false; } public loop = (): void => { const { onComplete, duration, expiresAt } = this.props; if (!this.looping) { return; } const ratio = getRatio(expiresAt, duration); this.setState({ ratio }); if (ratio === 1) { this.looping = false; if (onComplete) { onComplete(); } } else { requestAnimationFrame(this.loop); } }; public render(): JSX.Element { const { ratio } = this.state; const strokeDashoffset = ratio * CIRCUMFERENCE; return (
); } } function getRatio(expiresAt: number, duration: number) { const start = expiresAt - duration; const end = expiresAt; const now = Date.now(); const totalTime = end - start; const elapsed = now - start; return Math.min(Math.max(0, elapsed / totalTime), 1); }