Files
coder/site/src/hooks/useTime.ts
T
Jake Howell 82456ff62e feat: resolve useTime() thunk() error (#24234)
Fixes a regression introduced in #24060 that could crash the frontend.

`thunk` is created by `useEffectEvent()`, and React 19.2 enforces that
effect-event functions are not invoked during render. The previous code
called `thunk()` inside a `setState` updater function, and React
executes updater
functions during render, so this became an illegal render-phase call.

The fix computes `next` in the interval callback (`const next =
thunk()`) and then stores it via `setComputedValue(() => next)`. This
keeps the `useEffectEvent` call outside render and also preserves
correct behavior when `func` returns a function value, because React
stores `next` instead of treating it as a functional updater.
2026-04-10 10:45:18 +00:00

44 lines
1.1 KiB
TypeScript

import { useEffect, useEffectEvent, useState } from "react";
interface UseTimeOptions {
/**
* Can be set to `true` to disable checking for updates in circumstances where it is known
* that there is no work to do.
*/
disabled?: boolean;
/**
* The amount of time in milliseconds that should pass between checking for updates.
*/
interval?: number;
}
/**
* useTime allows a component to rerender over time without a corresponding state change.
* An example could be a relative timestamp (eg. "in 5 minutes") that should count down as it
* approaches.
*/
export function useTime<T>(func: () => T, options: UseTimeOptions = {}): T {
const [computedValue, setComputedValue] = useState(() => func());
const { disabled = false, interval = 1000 } = options;
const thunk = useEffectEvent(func);
useEffect(() => {
if (disabled) {
return;
}
const handle = setInterval(() => {
const next = thunk();
setComputedValue(() => next);
}, interval);
return () => {
clearInterval(handle);
};
}, [disabled, interval]);
return computedValue;
}