jotai
Version:
👻 Primitive and flexible state management for React
137 lines (136 loc) • 4.52 kB
JavaScript
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;
}