JSFiddle - React, Tailwind, and code Playground

by Josh Pullen

HTML

<!--
  Create timer that keeps you on track to do n questions in m minutes
  (default 23 questions in 35 minutes)
-->

JavaScript

import { html, render, useReducer, useEffect, useCallback } from "https://unpkg.com/htm/preact/standalone.module.js";

function App() {
	const { time, status, start, pause, reset } = useStopwatch();
  
  const safeReset = () => {
  	if (prompt("Are you sure you want to reset?")) {
    	reset();
    }
  }

	return html`
  	<div style=${{ width: "100vw", height: "100vh", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", overflowY: "auto" }}>
    	<h1>LSAT Timer</h1>
      <div>${time}</div>
      ${status === "off" && html`
      	<button onClick=${start}>Start</button>
        ${time > 0 && html`<button onClick=${safeReset}>Reset</button>`}
      `}
      ${status === "on" && html`
      	<button onClick=${pause}>Pause</button>
      `}
    </div>
  `;
}

function getTime(state) {
	switch (state.status) {
    case "off":
    	return state.startValue;
    case "on":
    	return state.startValue + (Date.now() - state.startTime) / 1000;
    default:
      return 0;
  }
}

function reducer(state, action) {
	switch (action.type) {
  	case "START":
      return {
        status: "on",
        startTime: Date.now(),
        startValue: getTime(state)
      }
    case "PAUSE":
    	return {
      	status: "off",
        startValue: getTime(state)
      }
    case "RESET":
    default:
    	return {
        status: "off",
        startValue: 0
      }
  }
}

function useConstantRerender(enabled = true) {
	const [, forceRerender] = useReducer(v => !v, true);
  
  useEffect(() => {
  	if (enabled) {
    	let killed = false;
    	const callback = () => {
        forceRerender();
        if (!killed) {
        	requestAnimationFrame(callback);
        }
      }
      
      requestAnimationFrame(callback);
      
      return () => {
        killed = true;
      }
    }
  }, [enabled]);
}

function useStopwatch() {
	const [state, dispatch] = useReducer(reducer, { status: "off", startValue: 0 });
  
  const start = useCallback(() => {
 ...