mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-15 20:05:13 +02:00
59 lines
1.6 KiB
TypeScript
59 lines
1.6 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
|
const STORAGE_PREFIX = 'nyx:';
|
|
|
|
function storageKey(key: string) {
|
|
return `${STORAGE_PREFIX}${key}`;
|
|
}
|
|
|
|
function read<T>(key: string, fallback: T): T {
|
|
try {
|
|
const raw = window.localStorage.getItem(storageKey(key));
|
|
if (raw === null) return fallback;
|
|
return JSON.parse(raw) as T;
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function write<T>(key: string, value: T): void {
|
|
try {
|
|
window.localStorage.setItem(storageKey(key), JSON.stringify(value));
|
|
} catch {
|
|
// Quota exceeded or storage disabled, so silently degrade.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `useState` that persists to `localStorage` under `nyx:<key>`.
|
|
*
|
|
* Suitable for view preferences (theme, sidebar collapse, default page size).
|
|
* Not suitable for sensitive data because `localStorage` is not encrypted.
|
|
*
|
|
* Cross-tab sync is not implemented; if the user opens two tabs they get
|
|
* independent state until next load. That's the common-case ergonomic.
|
|
*/
|
|
export function usePersistedState<T>(
|
|
key: string,
|
|
initial: T,
|
|
): [T, (next: T | ((prev: T) => T)) => void] {
|
|
const [state, setState] = useState<T>(() => read(key, initial));
|
|
|
|
// Avoid writing back the initial value on first mount when nothing changed.
|
|
const hydrated = useRef(false);
|
|
useEffect(() => {
|
|
if (!hydrated.current) {
|
|
hydrated.current = true;
|
|
return;
|
|
}
|
|
write(key, state);
|
|
}, [key, state]);
|
|
|
|
const set = useCallback((next: T | ((prev: T) => T)) => {
|
|
setState((prev) =>
|
|
typeof next === 'function' ? (next as (p: T) => T)(prev) : next,
|
|
);
|
|
}, []);
|
|
|
|
return [state, set];
|
|
}
|