Files
Netcatty/application/state/useStoredBoolean.ts
T
Meow-Emperor f9eded887f Fix global broadcast for orphan sessions
Keep stored boolean updates pure and propagate global broadcast state through terminal panes so orphan sessions can toggle and receive broadcasts correctly.
2026-09-05 00:11:52 +08:00

63 lines
2.6 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "react";
import { localStorageAdapter } from "../../infrastructure/persistence/localStorageAdapter";
/**
* Hook for persisting a boolean value to localStorage.
* Syncs across components in the same window via a custom event,
* and across windows via the native storage event.
* @param storageKey - The key to use for localStorage
* @param fallback - The default value if no stored value exists (defaults to false)
* @returns A tuple of [value, setValue] similar to useState
*/
export const useStoredBoolean = (
storageKey: string,
fallback: boolean = false,
) => {
const [value, setValue] = useState<boolean>(() => {
const stored = localStorageAdapter.readBoolean(storageKey);
return stored ?? fallback;
});
const valueRef = useRef(value);
const setAndPersist = useCallback((next: boolean | ((prev: boolean) => boolean)) => {
// Resolve functional updates before scheduling React state. The updater
// must stay pure because React may evaluate it while rendering.
const resolved = typeof next === "function" ? next(valueRef.current) : next;
valueRef.current = resolved;
setValue(resolved);
localStorageAdapter.writeBoolean(storageKey, resolved);
// Notify other same-window consumers outside the state updater.
window.dispatchEvent(
new CustomEvent("stored-boolean-change", { detail: { key: storageKey, value: resolved } }),
);
}, [storageKey]);
useEffect(() => {
// Sync from other components in the same window
const handleCustom = (e: Event) => {
const { key, value: newValue } = (e as CustomEvent).detail;
if (key === storageKey) {
valueRef.current = newValue;
setValue(newValue);
}
};
// Sync from other windows
const handleStorage = (e: StorageEvent) => {
if (e.key === storageKey) {
const stored = localStorageAdapter.readBoolean(storageKey);
const nextValue = stored ?? fallback;
valueRef.current = nextValue;
setValue(nextValue);
}
};
window.addEventListener("stored-boolean-change", handleCustom);
window.addEventListener("storage", handleStorage);
return () => {
window.removeEventListener("stored-boolean-change", handleCustom);
window.removeEventListener("storage", handleStorage);
};
}, [storageKey, fallback]);
return [value, setAndPersist] as const;
};