mirror of
https://github.com/coder/coder.git
synced 2026-11-08 05:32:55 +08:00
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.
44 lines
1.1 KiB
TypeScript
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;
|
|
}
|