import { useCallback, ReactNode } from "react"; import { DefaultValue } from "recoil"; import { ReadItem, WriteItems, ListenToItems, RecoilSync } from "recoil-sync"; import { STORAGE_KEY } from "."; const DEFAULT_VALUE = new DefaultValue(); export default ({ children }: { children: ReactNode }) => { const read: ReadItem = useCallback((itemKey) => { if (typeof document === "undefined") return DEFAULT_VALUE; // SSR const item = localStorage.getItem(itemKey); let parsed: unknown; try { parsed = item === null ? DEFAULT_VALUE : parseJSON(item); } catch { parsed = DEFAULT_VALUE; console.warn({ itemKey, item }, "parseJSON failed"); } return parsed; }, []); const write: WriteItems = useCallback(({ diff }) => { if (typeof document === "undefined") return; // SSR for (const [key, value] of diff) { if (value instanceof DefaultValue) { localStorage.removeItem(key); } else { // reasons for setItem to fail: https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#exceptions try { localStorage.setItem(key, JSON.stringify(value)); } catch (err) { console.warn({ err, key, value }, "localStorage.setItem failed"); } } } }, []); const listen: ListenToItems = useCallback( ({ updateItem, updateAllKnownItems }) => { void updateAllKnownItems; const onStorage = (event: StorageEvent) => { // ignore clear() calls if (event.storageArea === localStorage && event.key !== null) { let parsed: unknown; try { parsed = event.newValue === null ? DEFAULT_VALUE : parseJSON(event.newValue); } catch { parsed = DEFAULT_VALUE; console.warn({ event }, "parseJSON failed"); } updateItem(event.key, parsed); } }; window.addEventListener("storage", onStorage); return () => window.removeEventListener("storage", onStorage); }, [] ); return ( {children} ); }; function parseJSON(value: string): unknown { return value === "undefined" ? undefined : JSON.parse(value); }