Skip to content

Instantly share code, notes, and snippets.

@sillvva
Last active August 24, 2022 03:14
Show Gist options
  • Save sillvva/20d6e77e116787d2a37c6aab2d2295b6 to your computer and use it in GitHub Desktop.
Save sillvva/20d6e77e116787d2a37c6aab2d2295b6 to your computer and use it in GitHub Desktop.
Zod-validated, query-based useTimer Hook
import qs from "qs";
import { z, ZodSchema } from "zod";
import dayjs from "dayjs";
import duration from "dayjs/plugin/duration";
import { useEffect, useRef, useState } from "react";
dayjs.extend(duration);
const parseObjectPrimitives = (obj: Record<string, any>): any => {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => {
if (typeof v === "object") return [k, parseObjectPrimitives(v)];
if (!isNaN(v)) return [k, parseFloat(v)];
if (v === "true") return [k, true];
if (v === "false") return [k, false];
if (typeof v === "string") return [k, v];
return [k, null];
})
);
};
export function useZodValidatedQueryString<T>(schema: ZodSchema<T>) {
const queryString = window.location.search;
const parsed =
typeof queryString === "string"
? qs.parse(queryString, {
ignoreQueryPrefix: true
})
: queryString;
const zResult = schema.safeParse(parseObjectPrimitives(parsed));
return {
query: zResult.success ? zResult.data : ({} as T),
errors: !zResult.success
? zResult.error.issues.map(i => `${i.path.join(".")}: ${i.message}`).reduce((acc, v) => (acc.includes(v) ? acc : [...acc, v]), [] as string[])
: []
};
}
export function useTimer() {
const { query, errors } = useZodValidatedQueryString(z.object({
time: z.string().regex(/^\d{1,2}:\d{2}$/).nullable(),
}));
const d = useRef(new Date());
const [timeLeft, setTimeLeft] = useState(d.current.getTime() - Date.now());
useEffect(() => {
let interval: NodeJS.Timer;
if (query.time) {
const [hours, minutes] = query.time.split(":");
d.current.setHours(parseInt(hours || "0"), parseInt(minutes || "0"), 0, 0);
interval = setInterval(() => {
setTimeLeft(Math.max(0, d.current.getTime() - Date.now()));
}, 1000);
}
else {
setTimeLeft(0);
}
return () => clearInterval(interval);
}, [query.time]);
return {
timeLeft,
timer: dayjs.duration(timeLeft, "milliseconds").format("HH:mm:ss"),
errors
}
}
//////////////////////////////////
function App() {
const { timer, errors } = useTimer();
if (errors.length) return errors.map((error, i) => <div key={i}>{error}</div>);
return <div>{timer}</div>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment