'use client'; import {useCallback, useSyncExternalStore} from 'react'; /** * A boolean persisted in localStorage, read without a hydration mismatch. * * useSyncExternalStore already solves the hydration problem on its own: * React uses `getServerSnapshot` for the SSR render AND for the first client * render, so the two markups match, then immediately re-reads `getSnapshot` * and re-renders with the stored value. No `isHydrated` flag is needed — and * adding one means a setState inside an effect, which React's lint rule * rejects and which costs an extra render pass to do what the hook already * does natively. * * Writes notify every subscriber, so two components reading the same key never * disagree, and the `storage` event keeps other tabs in step. */ const listeners = new Set<() => void>(); function emit() { listeners.forEach((l) => l()); } function subscribe(onChange: () => void) { listeners.add(onChange); window.addEventListener('storage', onChange); return () => { listeners.delete(onChange); window.removeEventListener('storage', onChange); }; } /** * The same store, but "never set" stays distinguishable from "set to false". * * The sidebar needs that distinction: its default is per-device (a tablet * starts collapsed, a desktop starts expanded), so a stored `false` has to * mean "this merchant expanded it" and not "no preference yet". Collapsing * the two would either strand tablets expanded or force desktops to re-expand * on every first visit. */ export function usePersistentTriState( key: string, ): [boolean | null, (next: boolean) => void] { const getSnapshot = useCallback(() => { try { const raw = window.localStorage.getItem(key); return raw === null ? null : raw === 'true'; } catch { // Private mode / storage disabled — degrade to "no preference" rather // than taking the whole shell down over a preference. return null; } }, [key]); const getServerSnapshot = useCallback(() => null, []); const value = useSyncExternalStore( subscribe, getSnapshot, getServerSnapshot, ); const set = useCallback( (next: boolean) => { try { window.localStorage.setItem(key, String(next)); } catch { /* ignore — the notify below still updates this session */ } emit(); }, [key], ); return [value, set]; } export function usePersistentFlag( key: string, fallback = false, ): [boolean, (next: boolean) => void] { const [stored, set] = usePersistentTriState(key); return [stored ?? fallback, set]; }