diff --git a/src/mutableAtom/index.ts b/src/mutableAtom/index.ts index d6705f3..4a06d51 100644 --- a/src/mutableAtom/index.ts +++ b/src/mutableAtom/index.ts @@ -1,60 +1,48 @@ -import { atom } from 'jotai' -import type { PrimitiveAtom } from 'jotai' -import { proxy, snapshot, subscribe } from 'valtio' +import { atom } from 'jotai/vanilla' +import { proxy, snapshot, subscribe } from 'valtio/vanilla' import type { Action, ActionWithPayload, Options, ProxyState, Store, - Wrapped, } from './types' export function mutableAtom( - value: Value, + initialValue: Value, options: Options = defaultOptions ) { - const valueAtom = atom({ value }) + const valueAtom = atom({ value: initialValue }) if (process.env.NODE_ENV !== 'production') { valueAtom.debugPrivate = true } - return makeMutableAtom(valueAtom, options) -} -export function makeMutableAtom( - valueAtom: PrimitiveAtom>, - options: Options = defaultOptions -) { const { proxyFn } = { ...defaultOptions, ...options } const storeAtom = atom< Store, - [ActionWithPayload<'setValueAsync', Value> | Action<'mount'>], - void + [ActionWithPayload<'setValue', Value> | Action<'getValue'>], + void | Value >( - (get, { setSelf }) => ({ - proxyState: null, - getValue: () => get(valueAtom).value, - setValue: async (value: Value) => { - await defer() - setSelf({ type: 'setValueAsync', payload: value }) - }, - }), + (_get, { setSelf }) => { + const store: Store = { + proxyState: createProxyState(() => store), + getValue: () => setSelf({ type: 'getValue' }) as Value, + setValue: (value: Value) => + setSelf({ type: 'setValue', payload: value }) as void, + } + return store + }, (get, set, action) => { - if (action.type === 'setValueAsync') { + if (action.type === 'setValue') { set(valueAtom, { value: action.payload }) - } else if (action.type === 'mount') { - const store = get(storeAtom) - store.setValue = (value: Value) => { - set(valueAtom, { value }) - } + } else if (action.type === 'getValue') { + return get(valueAtom).value } } ) - storeAtom.onMount = (setAtom) => setAtom({ type: 'mount' }) - if (process.env.NODE_ENV !== 'production') { storeAtom.debugPrivate = true } @@ -65,9 +53,8 @@ export function makeMutableAtom( function onChange(getStore: () => Store) { return () => { const { proxyState, getValue, setValue } = getStore() - if (proxyState === null) return const { value } = snapshot(proxyState) - if (value !== getValue()) { + if (!Object.is(value, getValue())) { setValue(value as Awaited) } } @@ -77,22 +64,9 @@ export function makeMutableAtom( * create the proxy state and subscribe to it */ function createProxyState(getStore: () => Store) { - const store = getStore() - const value = store.getValue() - store.proxyState ??= proxyFn({ value }) - store.proxyState.value = value - subscribe(store.proxyState, onChange(getStore), true) - return store.proxyState - } - - /** - * return the proxy if it exists, otherwise create and subscribe to it - */ - function ensureProxyState(getStore: () => Store) { - const { proxyState } = getStore() - if (proxyState === null) { - return createProxyState(getStore) - } + const proxyState = proxyFn({ value: initialValue }) + // We never unsubscribe, but it's garbage collectable. + subscribe(proxyState, onChange(getStore), true) return proxyState } @@ -119,9 +93,8 @@ export function makeMutableAtom( */ const proxyEffectAtom = atom>((get) => { get(valueAtom) // subscribe to value updates - const getStore = () => get(storeAtom) - const proxyState = ensureProxyState(getStore) - return wrapProxyState(proxyState) + const store = get(storeAtom) + return wrapProxyState(store.proxyState) }) return proxyEffectAtom } @@ -129,10 +102,3 @@ export function makeMutableAtom( const defaultOptions = { proxyFn: proxy, } - -/** - * delays execution until next microtask - */ -function defer() { - return Promise.resolve().then() -} diff --git a/src/mutableAtom/types.ts b/src/mutableAtom/types.ts index 4e97150..62ab9db 100644 --- a/src/mutableAtom/types.ts +++ b/src/mutableAtom/types.ts @@ -1,13 +1,11 @@ -export type Wrapped = { value: T } +type Wrapped = { value: T } type ProxyFn = (obj: Wrapped) => Wrapped -type PromiseOrValue = Promise | T - export type Store = { - proxyState: ProxyState | null + proxyState: ProxyState getValue: () => Value - setValue: (value: Value) => PromiseOrValue + setValue: (value: Value) => void } export type Options = {