UNPKG

jotai

Version:

👻 Primitive and flexible state management for React

137 lines (136 loc) • 4.52 kB
import { atom } from '../../vanilla.js'; import { RESET } from './constants.js'; const isPromiseLike = (x) => typeof x?.then === 'function'; export function withStorageValidator(validator) { return (unknownStorage) => { const storage = { ...unknownStorage, getItem: (key, initialValue) => { const validate = (value) => { if (!validator(value)) { return initialValue; } return value; }; const value = unknownStorage.getItem(key, initialValue); if (isPromiseLike(value)) { return value.then(validate); } return validate(value); }, }; return storage; }; } export function createJSONStorage(getStringStorage = () => { try { return window.localStorage; } catch (e) { if (process.env.NODE_ENV !== 'production') { if (typeof window !== 'undefined') { console.warn(e); } } return undefined; } }, options) { let lastStr; let lastValue; const storage = { getItem: (key, initialValue) => { const parse = (str) => { str = str || ''; if (lastStr !== str) { try { lastValue = JSON.parse(str, options?.reviver); } catch { return initialValue; } lastStr = str; } return lastValue; }; const str = getStringStorage()?.getItem(key) ?? null; if (isPromiseLike(str)) { return str.then(parse); } return parse(str); }, setItem: (key, newValue) => getStringStorage()?.setItem(key, JSON.stringify(newValue, options?.replacer)), removeItem: (key) => getStringStorage()?.removeItem(key), }; const createHandleSubscribe = (subscriber) => (key, callback, initialValue) => subscriber(key, (v) => { let newValue; try { newValue = JSON.parse(v || '', options?.reviver); } catch { newValue = initialValue; } callback(newValue); }); let subscriber; try { subscriber = getStringStorage()?.subscribe; } catch { // ignore } if (!subscriber && typeof window !== 'undefined' && typeof window.addEventListener === 'function' && window.Storage) { subscriber = (key, callback) => { if (!(getStringStorage() instanceof window.Storage)) { return () => { }; } const storageEventCallback = (e) => { if (e.storageArea === getStringStorage() && e.key === key) { callback(e.newValue); } }; window.addEventListener('storage', storageEventCallback); return () => { window.removeEventListener('storage', storageEventCallback); }; }; } if (subscriber) { storage.subscribe = createHandleSubscribe(subscriber); } return storage; } const defaultStorage = createJSONStorage(); export function atomWithStorage(key, initialValue, storage = defaultStorage, options) { const getOnInit = options?.getOnInit; const baseAtom = atom(getOnInit ? storage.getItem(key, initialValue) : initialValue); if (process.env.NODE_ENV !== 'production') { baseAtom.debugPrivate = true; } baseAtom.onMount = (setAtom) => { setAtom(storage.getItem(key, initialValue)); return storage.subscribe?.(key, setAtom, initialValue); }; const anAtom = atom((get) => get(baseAtom), (get, set, update) => { const nextValue = typeof update === 'function' ? update(get(baseAtom)) : update; if (nextValue === RESET) { set(baseAtom, initialValue); return storage.removeItem(key); } if (isPromiseLike(nextValue)) { return nextValue.then((resolvedValue) => { set(baseAtom, resolvedValue); return storage.setItem(key, resolvedValue); }); } set(baseAtom, nextValue); return storage.setItem(key, nextValue); }); return anAtom; }