import { useState, useEffect, useRef, type Dispatch, type SetStateAction } from 'react'; const PREFIX = 'wing'; function buildKey(userId: number, name: string): string { return `${PREFIX}:${userId}:${name}`; } function readStorage(key: string, fallback: T): T { try { const raw = localStorage.getItem(key); if (raw === null) return fallback; return JSON.parse(raw) as T; } catch { return fallback; } } function writeStorage(key: string, value: T): void { try { localStorage.setItem(key, JSON.stringify(value)); } catch { // quota exceeded or unavailable — silent } } function resolveDefault(d: T | (() => T)): T { return typeof d === 'function' ? (d as () => T)() : d; } /** * useState와 동일한 API, localStorage 자동 동기화. * * @param userId null이면 일반 useState처럼 동작 (비영속) * @param name 설정 이름 (e.g. 'typeEnabled') * @param defaultValue 초기값 또는 lazy initializer * @param debounceMs localStorage 쓰기 디바운스 (기본 300ms) */ export function usePersistedState( userId: number | null, name: string, defaultValue: T | (() => T), debounceMs = 300, ): [T, Dispatch>] { const resolved = resolveDefault(defaultValue); const [state, setState] = useState(() => { if (userId == null) return resolved; return readStorage(buildKey(userId, name), resolved); }); const timerRef = useRef | null>(null); const stateRef = useRef(state); const userIdRef = useRef(userId); const nameRef = useRef(name); stateRef.current = state; userIdRef.current = userId; nameRef.current = name; // userId 변경 시 해당 사용자의 저장값 재로드 useEffect(() => { if (userId == null) return; const stored = readStorage(buildKey(userId, name), resolved); setState(stored); // eslint-disable-next-line react-hooks/exhaustive-deps }, [userId]); // debounced write useEffect(() => { if (userId == null) return; const key = buildKey(userId, name); if (timerRef.current != null) clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { writeStorage(key, state); timerRef.current = null; }, debounceMs); return () => { if (timerRef.current != null) { clearTimeout(timerRef.current); timerRef.current = null; } }; }, [state, userId, name, debounceMs]); // unmount 시 pending write flush useEffect(() => { return () => { if (timerRef.current != null) { clearTimeout(timerRef.current); timerRef.current = null; } if (userIdRef.current != null) { writeStorage(buildKey(userIdRef.current, nameRef.current), stateRef.current); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return [state, setState]; }