jotai-valtio
Version:
79 lines (78 loc) • 2.59 kB
JavaScript
import { atom } from 'jotai/vanilla';
import { proxy, snapshot, subscribe } from 'valtio/vanilla';
import { deepClone } from 'valtio/vanilla/utils';
export function mutableAtom(initialValue, options = defaultOptions) {
const valueAtom = atom({ value: initialValue });
if (process.env.NODE_ENV !== 'production') {
valueAtom.debugPrivate = true;
}
const { proxyFn } = { ...defaultOptions, ...options };
const storeAtom = atom((_get, { setSelf }) => {
const store = {
proxyState: createProxyState(() => store),
getValue: () => setSelf({ type: 'getValue' }),
setValue: (value) => setSelf({ type: 'setValue', payload: value }),
};
return store;
}, (get, set, action) => {
if (action.type === 'setValue') {
set(valueAtom, { value: action.payload });
}
else if (action.type === 'getValue') {
return get(valueAtom).value;
}
});
if (process.env.NODE_ENV !== 'production') {
storeAtom.debugPrivate = true;
}
/**
* sync the proxy state with the atom
*/
function onChange(getStore) {
return () => {
const { proxyState, getValue, setValue } = getStore();
const { value } = snapshot(proxyState);
if (!Object.is(value, getValue())) {
setValue(value);
}
};
}
/**
* create the proxy state and subscribe to it
*/
function createProxyState(getStore) {
const proxyState = proxyFn({ value: deepClone(initialValue) });
// We never unsubscribe, but it's garbage collectable.
subscribe(proxyState, onChange(getStore), true);
return proxyState;
}
/**
* wrap the proxy state in a proxy to ensure rerender on value change
*/
function wrapProxyState(proxyState) {
return new Proxy(proxyState, {
get(target, property) {
return target[property];
},
set(target, property, value) {
if (property === 'value') {
target[property] = value;
return true;
}
return false;
},
});
}
/**
* create an atom that returns the proxy state
*/
const proxyEffectAtom = atom((get) => {
get(valueAtom); // subscribe to value updates
const store = get(storeAtom);
return wrapProxyState(store.proxyState);
});
return proxyEffectAtom;
}
const defaultOptions = {
proxyFn: proxy,
};