69 lines
1.9 KiB
TypeScript
69 lines
1.9 KiB
TypeScript
"use client";
|
||
|
||
import { useCallback, useSyncExternalStore } from "react";
|
||
|
||
type Listener = () => void;
|
||
|
||
const listeners = new Map<string, Set<Listener>>();
|
||
const cache = new Map<string, unknown>();
|
||
|
||
function readFromStorage<T>(key: string, initial: T): T {
|
||
try {
|
||
const raw = window.localStorage.getItem(key);
|
||
return raw != null ? (JSON.parse(raw) as T) : initial;
|
||
} catch {
|
||
return initial;
|
||
}
|
||
}
|
||
|
||
function notify(key: string) {
|
||
listeners.get(key)?.forEach((l) => l());
|
||
}
|
||
|
||
/**
|
||
* State, der in localStorage persistiert wird.
|
||
* SSR-sicher über useSyncExternalStore: liefert serverseitig `initial`,
|
||
* hydriert clientseitig aus localStorage.
|
||
*/
|
||
export function useLocalStorage<T>(key: string, initial: T) {
|
||
const subscribe = useCallback(
|
||
(callback: Listener) => {
|
||
let set = listeners.get(key);
|
||
if (!set) {
|
||
set = new Set();
|
||
listeners.set(key, set);
|
||
}
|
||
set.add(callback);
|
||
return () => set!.delete(callback);
|
||
},
|
||
[key],
|
||
);
|
||
|
||
const getSnapshot = useCallback(() => {
|
||
if (!cache.has(key)) cache.set(key, readFromStorage(key, initial));
|
||
return cache.get(key) as T;
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps -- `initial` is only used as a one-time fallback
|
||
}, [key]);
|
||
|
||
const getServerSnapshot = useCallback(() => initial, [initial]);
|
||
|
||
const value = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||
|
||
const setValue = useCallback(
|
||
(next: T | ((prev: T) => T)) => {
|
||
const prev = cache.has(key) ? (cache.get(key) as T) : readFromStorage(key, initial);
|
||
const resolved = typeof next === "function" ? (next as (p: T) => T)(prev) : next;
|
||
cache.set(key, resolved);
|
||
try {
|
||
window.localStorage.setItem(key, JSON.stringify(resolved));
|
||
} catch {
|
||
// Speicher voll o.ä. – egal
|
||
}
|
||
notify(key);
|
||
},
|
||
[key, initial],
|
||
);
|
||
|
||
return [value, setValue] as const;
|
||
}
|