@zedux/atoms
Version:
A Molecular State Engine for React
122 lines (121 loc) • 5 kB
JavaScript
import { createStore, zeduxTypes } from '@zedux/core';
import { createInjector } from '../factories/createInjector.js';
import { prefix } from '../utils/index.js';
export const doSubscribe = (instance, store) => store.subscribe((newState, oldState, 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._scheduleEvaluation({
newState,
oldState,
operation: 'injectStore',
reasons: [
{
action,
newState,
oldState,
operation: 'dispatch',
sourceType: 'Store',
type: 'state changed',
},
],
sourceType: 'Injector',
type: 'state changed',
}, isBatch);
// run the scheduler synchronously after every store update unless batching
if (!isBatch) {
instance.ecosystem._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 '...js'
*
* 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 = createInjector('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.ecosystem.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;
});