(value: boolean, options: StableFlagOptions = {})
| 99 | * connection/loading indicators that would otherwise flicker on sub-second changes. |
| 100 | */ |
| 101 | export function useStableFlag(value: boolean, options: StableFlagOptions = {}): boolean { |
| 102 | const [active, setActive] = useState(false) |
| 103 | const { delayMs = 0, minVisibleMs = 0 } = options |
| 104 | const valueRef = useRef(value) |
| 105 | valueRef.current = value |
| 106 | const controllerRef = useRef<ReturnType<typeof createStableFlagController> | null>(null) |
| 107 | |
| 108 | useEffect(() => { |
| 109 | // Reset to the fresh controller's baseline. Without this, recreating the |
| 110 | // controller on an options change while `active` is true and `value` is |
| 111 | // already false would strand the React state at true — the new controller |
| 112 | // starts internally false, so its `setValue(false)` early-returns and never |
| 113 | // emits `onChange(false)`. |
| 114 | setActive(false) |
| 115 | const controller = createStableFlagController(setActive, { delayMs, minVisibleMs }) |
| 116 | controllerRef.current = controller |
| 117 | controller.setValue(valueRef.current) |
| 118 | return () => { |
| 119 | controller.dispose() |
| 120 | controllerRef.current = null |
| 121 | } |
| 122 | }, [delayMs, minVisibleMs]) |
| 123 | |
| 124 | useEffect(() => { |
| 125 | controllerRef.current?.setValue(value) |
| 126 | }, [value]) |
| 127 | |
| 128 | return active |
| 129 | } |
no test coverage detected