@zedux/stores
Version:
The legacy composable store model of Zedux
106 lines (105 loc) • 4.54 kB
JavaScript
import { createStore, zeduxTypes } from '@zedux/core';
import { zi } from '@zedux/atoms';
import { prefix } from './atoms-port.js';
export const doSubscribe = (instance, store) => store.subscribe((newState, p, action) => {
// Nothing to do if the state hasn't changed. Also, ignore state updates
// during evaluation. TODO: Create an ecosystem-level flag to turn on
// warning logging for state-updates-during-evaluation, since this may be
// considered an anti-pattern.
if (instance._isEvaluating || action.meta === zeduxTypes.ignore) {
return;
}
const isBatch = (action === null || action === void 0 ? void 0 : action.meta) === zeduxTypes.batch;
instance.r({ p }, isBatch);
// run the scheduler synchronously after every store update unless batching
if (!isBatch) {
instance.e._scheduler.flush();
}
});
/**
* injectStore()
*
* A convenience utility for quickly creating and optionally subscribing to
* stores in atoms.
*
* The returned store is a stable reference - it will not change on subsequent
* evaluations. It can therefore be returned from the instance factory as the
* instance's store. It also doesn't _need_ to be added to injector deps arrays
* (though there's no harm in doing so).
*
* Accepts either the initial store state or a function that returns the store.
* Use the latter for maximum flexibility.
*
* Subscribes to the store by default, causing the atom to be reevaluated on
* every state change. This can be changed by passing `subscribe: false`.
*
* In most cases you won't need to prevent subscribing. But it can be a useful
* performance optimization.
*
* ```ts
* import { atom, injectStore } from '@zedux/atoms'
*
* const inputAtom = atom('input', () => {
* const store = injectStore('', { subscribe: false })
*
* return store
* })
* ```
*
* When `hydrate: true` is passed, the store's initial state will be set to the
* value from the last call to `ecosystem.hydrate()` whose key matches this atom
* instance. The hydrated value will be passed to the atom's `hydrate` config
* option, if any, to transform the value first.
*
* When the function `storeFactory` overload is used and `hydrate: true` is
* passed, the transformed hydration will be passed to the store factory
* function and it's up to you to use it to hydrate the store you create.
*
* ```ts
* const store = injectStore(
* hydration => createStore(null, hydration ?? defaultVal),
* { hydrate: true }
* )
* // or simply:
* const store = injectStore(defaultVal, { hydrate: true })
* ```
*
* @param storeFactory - Either a function that returns a store or the initial
* state of the store
* @param config - A config object. Accepts the following properties:
* - `hydrate` - Whether to try hydrating this store with
* - `subscribe` - Whether to subscribe to the store (default: `true`)
* @returns Store
*/
export const injectStore = zi.c('injectStore', (instance, storeFactory, config) => {
var _a, _b;
const subscribe = (_a = config === null || config === void 0 ? void 0 : config.subscribe) !== null && _a !== void 0 ? _a : true;
const getStore = typeof storeFactory === 'function'
? storeFactory
: (hydration) => createStore(null, hydration !== null && hydration !== void 0 ? hydration : storeFactory);
const store = getStore((config === null || config === void 0 ? void 0 : config.hydrate) ? (_b = instance.e.hydration) === null || _b === void 0 ? void 0 : _b[instance.id] : undefined);
const subscription = subscribe && doSubscribe(instance, store);
return {
cleanup: subscription ? () => subscription.unsubscribe() : undefined,
result: store,
type: `${prefix}/store`,
};
}, (prevDescriptor, instance, storeFactory, config) => {
var _a;
const subscribe = (_a = config === null || config === void 0 ? void 0 : config.subscribe) !== null && _a !== void 0 ? _a : true;
const prevsubscribe = !!prevDescriptor.cleanup;
if (prevsubscribe === subscribe)
return prevDescriptor;
// we were subscribed, now we're not
if (!subscribe) {
// cleanup must be defined here. This cast is fine:
;
prevDescriptor.cleanup();
prevDescriptor.cleanup = undefined;
return prevDescriptor;
}
// we weren't subscribed, now we are
const subscription = doSubscribe(instance, prevDescriptor.result);
prevDescriptor.cleanup = () => subscription.unsubscribe();
return prevDescriptor;
});