Files
frigate/web/src/hooks/use-optimistic-state.ts
T

49 lines
1.9 KiB
TypeScript
Raw Normal View History

2026-05-07 08:53:07 -05:00
import { useState, useEffect, useCallback } from "react";
2024-04-04 10:09:19 -05:00
type OptimisticStateResult<T> = [T, (newValue: T) => void];
const useOptimisticState = <T>(
2024-05-27 06:59:26 -06:00
currentState: T,
2024-04-04 10:09:19 -05:00
setState: (newValue: T) => void,
delay: number = 20,
): OptimisticStateResult<T> => {
2024-05-27 06:59:26 -06:00
const [optimisticValue, setOptimisticValue] = useState<T>(currentState);
2024-04-04 10:09:19 -05:00
2026-05-07 08:53:07 -05:00
const handleValueChange = useCallback((newValue: T) => {
// Update the optimistic value immediately
setOptimisticValue(newValue);
2024-04-04 10:09:19 -05:00
}, []);
2026-05-07 08:53:07 -05:00
// Push the optimistic value to the real setter after the delay. Scoping
// this to an effect keyed on optimisticValue ensures the cleanup only
// cancels the timer for the value it scheduled — so StrictMode's
// effect-rerun (and future re-running mechanisms) reschedules cleanly
// instead of dropping the pending update on the floor.
2024-05-27 06:59:26 -06:00
useEffect(() => {
2026-05-07 08:53:07 -05:00
if (Object.is(optimisticValue, currentState)) {
return;
}
const id = setTimeout(() => setState(optimisticValue), delay);
return () => clearTimeout(id);
}, [optimisticValue, currentState, delay, setState]);
// External updates to currentState should win over a stale optimistic value.
// The guard matters under StrictMode: this effect's re-run captures the
// *old* currentState in its closure, so without the equality check it
// would clobber an optimistic update that another effect (e.g. a search
// param sync) made earlier in the same commit.
useEffect(() => {
if (!Object.is(currentState, optimisticValue)) {
2024-05-27 06:59:26 -06:00
setOptimisticValue(currentState);
}
// sometimes an external action will cause the currentState to change
// without handleValueChange being called. In this case
// we need to update the optimistic value so the UI reflects the change
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentState]);
2024-04-04 10:09:19 -05:00
return [optimisticValue, handleValueChange];
};
export default useOptimisticState;