Comparing version 0.2.1 to 0.3.0
@@ -1,2 +0,2 @@ | ||
import { ActionContext } from './dispatcher'; | ||
import { DispatchContext } from './dispatcher'; | ||
import { Store } from './store'; | ||
@@ -14,19 +14,11 @@ declare type ActionReturn<State> = State | void; | ||
name?: String; | ||
/** | ||
* arguments when this action called | ||
* @internal | ||
*/ | ||
args: any[]; | ||
}; | ||
export declare type Action<State> = ActionBase<State> & { | ||
call(ctx: ActionContext<State>): ActionReturn<State>; | ||
export declare type Action<State, Args extends any[]> = ActionBase<State> & { | ||
fn: ActionFunction<State, Args>; | ||
}; | ||
export declare type ActionPromise<State> = ActionBase<State> & { | ||
call(ctx: ActionContext<State>): Promise<ActionReturn<State>>; | ||
export declare type ActionPromise<State, Args extends any[]> = ActionBase<State> & { | ||
fn: ActionPromiseFunction<State, Args>; | ||
}; | ||
export declare type ActionCreator<State, Args extends any[]> = (ctx: ActionContext<State>, ...args: Args) => ActionReturn<State>; | ||
export declare type ActionPromiseCreator<State, Args extends any[]> = (ctx: ActionContext<State>, ...args: Args) => Promise<State>; | ||
export declare type ActionPromiseCreatorVoid<State, Args extends any[]> = (ctx: ActionContext<State>, ...args: Args) => Promise<void>; | ||
export declare type ActionFunction<State, Args extends any[]> = (...args: Args) => Action<State>; | ||
export declare type ActionPromiseFunction<State, Args extends any[]> = (...args: Args) => ActionPromise<State>; | ||
export declare type ActionFunction<State, Args extends any[]> = (ctx: DispatchContext<State>, ...args: Args) => ActionReturn<State>; | ||
export declare type ActionPromiseFunction<State, Args extends any[]> = (ctx: DispatchContext<State>, ...args: Args) => Promise<State | void>; | ||
/** | ||
@@ -39,12 +31,4 @@ * Create synchronous action. | ||
*/ | ||
export declare function createAction<State, Args extends any[]>(store: Store<State>, action: ActionCreator<State, Args>, name?: string): ActionFunction<State, Args>; | ||
export declare function createAction<State, Args extends any[]>(store: Store<State>, action: ActionFunction<State, Args>, name?: string): Action<State, Args>; | ||
/** | ||
* This is asynchornouse version of `createAction()` that return a promise to new state | ||
* | ||
* @param store store that associated to this action | ||
* @param action function that manipulate the state of the store | ||
* @param name this is only for debugging purpose. | ||
*/ | ||
export declare function createAction<State, Args extends any[]>(store: Store<State>, action: ActionPromiseCreator<State, Args>, name?: string): ActionPromiseFunction<State, Args>; | ||
/** | ||
* This is asynchornouse version of `createAction()` | ||
@@ -56,3 +40,3 @@ * | ||
*/ | ||
export declare function createAction<State, Args extends any[]>(store: Store<State>, action: ActionPromiseCreatorVoid<State, Args>, name?: string): ActionPromiseFunction<State, Args>; | ||
export declare function createAction<State, Args extends any[]>(store: Store<State>, action: ActionPromiseFunction<State, Args>, name?: string): ActionPromise<State, Args>; | ||
export {}; |
@@ -1,2 +0,2 @@ | ||
import { SubscriptionSet } from './provider'; | ||
import { SubscriptionSet } from './manager'; | ||
export declare type DevTool = { | ||
@@ -3,0 +3,0 @@ log(action: object): void; |
@@ -1,8 +0,21 @@ | ||
import { Action, ActionFunction, ActionPromise, ActionPromiseFunction } from './action'; | ||
import { Action, ActionPromise } from './action'; | ||
import { StoreManager } from './manager'; | ||
import { Store } from './store'; | ||
export interface Dispatcher { | ||
<State>(action: Action<State>): void; | ||
<State>(action: ActionPromise<State>): Promise<void>; | ||
/** | ||
* Invoke synchronous action | ||
* | ||
* @param action action that will be invoked | ||
* @param args action arguments | ||
*/ | ||
<State, Args extends any[]>(action: Action<State, Args>, ...args: Args): void; | ||
/** | ||
* Invoke asynchronous action | ||
* | ||
* @param action action that will be invoked | ||
* @param args action arguments | ||
*/ | ||
<State, Args extends any[]>(action: ActionPromise<State, Args>, ...args: Args): Promise<void>; | ||
} | ||
export declare type ActionContext<State> = { | ||
declare type DispatchContextBase<State> = { | ||
/** | ||
@@ -43,2 +56,13 @@ * State when the action is called. For complex action, please use {@link ActionContext.getState | getState} | ||
}; | ||
declare type MergeableDispatchContext<State> = DispatchContextBase<State> & { | ||
/** | ||
* Shallow merge current state with `partialState`. | ||
* Only valid for object based State | ||
* | ||
* @param partialState object that will be merged to current state | ||
* @param forceCommit if true, commit all changes after merging | ||
*/ | ||
mergeState(partialState: State extends object ? Partial<State> : never, forceCommit?: boolean): void; | ||
}; | ||
export declare type DispatchContext<State> = State extends object ? MergeableDispatchContext<State> : DispatchContextBase<State>; | ||
declare type StateUpdater<State> = State | StateUpdaterFunction<State>; | ||
@@ -52,7 +76,3 @@ declare type StateUpdaterFunction<State> = (prev: DeepReadonly<State>) => State; | ||
}; | ||
export declare function useDispatcher(): Dispatcher; | ||
export declare function useAction<State>(action: Action<State>): () => void; | ||
export declare function useAction<State>(action: ActionPromise<State>): () => Promise<void>; | ||
export declare function useAction<State, Args extends any[]>(action: ActionFunction<State, Args>): (...args: Args) => void; | ||
export declare function useAction<State, Args extends any[]>(action: ActionPromiseFunction<State, Args>): (...args: Args) => Promise<void>; | ||
export declare function createDispatcher(manager: StoreManager): Dispatcher; | ||
export {}; |
@@ -5,52 +5,18 @@ 'use strict'; | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var reactDom = require('react-dom'); | ||
var React = require('react'); | ||
var reactDom = require('react-dom'); | ||
var React__default = _interopDefault(React); | ||
var defaultComparator = _interopDefault(require('fast-deep-equal')); | ||
function createAction(store, action, name) { | ||
return function () { | ||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
return { | ||
store: store, | ||
call: function call(ctx) { | ||
return action.apply(void 0, [ctx].concat(args)); | ||
}, | ||
name: name, | ||
args: args | ||
}; | ||
// real implementation | ||
function createAction(store, fn, name) { | ||
return { | ||
store: store, | ||
fn: fn, | ||
name: name | ||
}; | ||
} | ||
function strictComparator(a, b) { | ||
return a === b; | ||
} | ||
function arrayComparator(a, b) { | ||
if (a.length !== b.length) { | ||
return false; | ||
} | ||
for (var i in a) { | ||
if (a[i] !== b[i]) return false; | ||
} | ||
return true; | ||
} | ||
function _objectWithoutPropertiesLoose(source, excluded) { | ||
if (source == null) return {}; | ||
var target = {}; | ||
var sourceKeys = Object.keys(source); | ||
var key, i; | ||
for (i = 0; i < sourceKeys.length; i++) { | ||
key = sourceKeys[i]; | ||
if (excluded.indexOf(key) >= 0) continue; | ||
target[key] = source[key]; | ||
} | ||
return target; | ||
} | ||
function initDevTool(states, subscriptions) { | ||
@@ -120,7 +86,118 @@ | ||
var Context = | ||
/*#__PURE__*/ | ||
React.createContext(undefined); | ||
function createDispatcher(manager) { | ||
return function (action) { | ||
var changesMap = new Map(); | ||
function createManager(initialStates, enableDevTool) { | ||
var commit = function commit() { | ||
changesMap.size > 0 && manager.commit(changesMap); | ||
changesMap.clear(); | ||
}; // create child dispatcher once | ||
var childDispatcher = function childDispatcher(childAction) { | ||
for (var _len2 = arguments.length, childArgs = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | ||
childArgs[_key2 - 1] = arguments[_key2]; | ||
} | ||
return dispatch(childAction, childArgs, false); | ||
}; | ||
var rootDone = false; | ||
function dispatch(childAction, childArgs, isRoot) { | ||
var autoCommit = isRoot; | ||
var storeName = childAction.store.name; | ||
var getState = function getState() { | ||
return changesMap.get(storeName) || manager.getState(childAction.store); | ||
}; | ||
var setState = function setState(updater, forceCommit) { | ||
if (forceCommit === void 0) { | ||
forceCommit = false; | ||
} | ||
var state = isStateUpdaterFunction(updater) ? updater(getState()) : updater; | ||
changesMap.set(storeName, state); | ||
if (forceCommit) { | ||
commit(); | ||
} | ||
}; | ||
var ctx = { | ||
state: getState(), | ||
getState: getState, | ||
setState: setState, | ||
mergeState: function mergeState(partialState, forceCommit) { | ||
var state = getState(); | ||
if (typeof state !== 'object' || Array.isArray(state)) { | ||
throw new Error('Merge state only available for object based state'); | ||
} | ||
setState(Object.assign(Object.create(null), state, partialState), forceCommit); | ||
}, | ||
commit: commit, | ||
disableAutoCommit: function disableAutoCommit() { | ||
autoCommit = false; | ||
}, | ||
getStore: function getStore(otherStore) { | ||
return changesMap.get(otherStore.name) || manager.getState(otherStore); | ||
}, | ||
dispatch: childDispatcher | ||
}; | ||
var result = childAction.fn.apply(childAction, [ctx].concat(childArgs)); | ||
var handleResult = function handleResult(newState) { | ||
if (newState !== undefined) { | ||
setState(newState); | ||
} | ||
if (autoCommit || rootDone) { | ||
commit(); | ||
} | ||
if ( manager.devTool) { | ||
var type = childAction.store.name + "." + (childAction.name || '<unknown>'); | ||
if (!autoCommit) { | ||
type += ' (deferred)'; | ||
} | ||
manager.devTool.log({ | ||
type: type, | ||
args: childArgs | ||
}); | ||
} | ||
if (isRoot) { | ||
rootDone = true; | ||
} | ||
}; | ||
if (isPromise(result)) { | ||
return result.then(handleResult); | ||
} else { | ||
return handleResult(result); | ||
} | ||
} | ||
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
args[_key - 1] = arguments[_key]; | ||
} | ||
return dispatch(action, args, true); | ||
}; | ||
} | ||
function isPromise(p) { | ||
return typeof p === 'object' && 'then' in p && typeof p.then === 'function'; | ||
} | ||
function isStateUpdaterFunction(updater) { | ||
return typeof updater === 'function'; | ||
} | ||
function createStoreManager(initialStates, enableDevTool) { | ||
var states = initialStates || new Map(); | ||
@@ -148,3 +225,3 @@ var subscriptions = new Map(); | ||
} else { | ||
var state = isInitiatorFunction(store.initialState) ? store.initialState() : store.initialState; | ||
var state = store.initState(); | ||
states.set(name, state); | ||
@@ -174,4 +251,6 @@ return state; | ||
} | ||
} | ||
}, | ||
dispatcher: undefined | ||
}; | ||
manager.dispatcher = createDispatcher(manager); | ||
@@ -185,2 +264,21 @@ if (enableDevTool) { | ||
function _objectWithoutPropertiesLoose(source, excluded) { | ||
if (source == null) return {}; | ||
var target = {}; | ||
var sourceKeys = Object.keys(source); | ||
var key, i; | ||
for (i = 0; i < sourceKeys.length; i++) { | ||
key = sourceKeys[i]; | ||
if (excluded.indexOf(key) >= 0) continue; | ||
target[key] = source[key]; | ||
} | ||
return target; | ||
} | ||
var Context = | ||
/*#__PURE__*/ | ||
React__default.createContext(undefined); | ||
var StoreManagerProvider = Context.Provider; | ||
var StoreProvider = function StoreProvider(_ref) { | ||
@@ -192,3 +290,3 @@ var initialStates = _ref.initialStates, | ||
var manager = React.useMemo(function () { | ||
return createManager(initialStates, enableDevTool); | ||
return createStoreManager(initialStates, enableDevTool); | ||
}, [initialStates, enableDevTool]); | ||
@@ -201,4 +299,4 @@ React.useEffect(function () { | ||
}; | ||
}); | ||
return React.createElement(Context.Provider, Object.assign({}, props, { | ||
}, [manager]); | ||
return React__default.createElement(StoreManagerProvider, Object.assign({}, props, { | ||
value: manager | ||
@@ -211,3 +309,3 @@ })); | ||
if (!ctx) { | ||
throw new Error('Not inside provider'); | ||
throw new Error('You must wrap the component with <StoreProvider>'); | ||
} | ||
@@ -218,86 +316,123 @@ | ||
function isInitiatorFunction(x) { | ||
return typeof x === 'function'; | ||
} | ||
function useStoreSubscription(_ref) { | ||
var manager = _ref.manager, | ||
stores = _ref.stores, | ||
getCurrentResult = _ref.getCurrentResult, | ||
comparator = _ref.comparator; | ||
// use ref to store current result and initialize the value. | ||
// the type can not be `Return` since it may be undefined | ||
// we need to make sure that current.value === undefined only | ||
// if it isn't initialized | ||
var value = React.useRef(); | ||
function createDispatcher(manager, root) { | ||
return function (action) { | ||
var _ref; | ||
if (value.current === undefined) { | ||
value.current = { | ||
result: getCurrentResult() | ||
}; | ||
} // tell react to rerender this component | ||
// should be called after updating `value.current` | ||
var autoCommit = !root; // root dispatcher | ||
var storeName = action.store.name; | ||
var changesMap = (_ref = root === null || root === void 0 ? void 0 : root.changesMap) !== null && _ref !== void 0 ? _ref : new Map(); | ||
var commit = root ? root.commit : function () { | ||
manager.commit(changesMap); | ||
changesMap.clear(); | ||
}; | ||
var _useState = React.useState({}), | ||
forceRerender = _useState[1]; // compare value.current with value from store manager | ||
// if it changed, update the value and force rerender | ||
var getState = function getState() { | ||
return changesMap.get(storeName) || manager.getState(action.store); | ||
}; | ||
var setState = function setState(updater, forceCommit) { | ||
if (forceCommit === void 0) { | ||
forceCommit = false; | ||
} | ||
var updateResultAndForceRender = React.useCallback(function () { | ||
var result = getCurrentResult(); | ||
var equal = comparator(value.current.result, result); | ||
var state = isStateUpdaterFunction(updater) ? updater(getState()) : updater; | ||
changesMap.set(storeName, state); | ||
if (!equal) { | ||
value.current = { | ||
result: result | ||
}; | ||
forceRerender({}); | ||
} // eslint-disable-next-line react-hooks/exhaustive-deps | ||
if (forceCommit) { | ||
commit(); | ||
} | ||
}; | ||
}, [value, getCurrentResult, forceRerender]); // comparator must be pure function | ||
var result = action.call({ | ||
state: getState(), | ||
getState: getState, | ||
setState: setState, | ||
commit: commit, | ||
disableAutoCommit: function disableAutoCommit() { | ||
autoCommit = false; | ||
}, | ||
getStore: function getStore(otherStore) { | ||
return changesMap.get(otherStore.name) || manager.getState(otherStore); | ||
}, | ||
dispatch: createDispatcher(manager, { | ||
changesMap: changesMap, | ||
commit: commit | ||
}) | ||
React.useEffect(function () { | ||
// maybe the state was changed while this component is being rendered | ||
updateResultAndForceRender(); // subscribe to all stores change | ||
// and return function to unscribe them | ||
var unsubscribeFunctions = stores.current.map(function (store) { | ||
return manager.subscribe(store, updateResultAndForceRender); | ||
}); | ||
return function () { | ||
return unsubscribeFunctions.forEach(function (fn) { | ||
return fn(); | ||
}); | ||
}; // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [manager, updateResultAndForceRender].concat(stores.current)); // but not its values | ||
var handleResult = function handleResult(newState) { | ||
if (newState !== undefined) { | ||
setState(newState); | ||
} | ||
return value.current.result; | ||
} | ||
if (autoCommit) { | ||
commit(); | ||
} | ||
/** | ||
* Create react hook to use one or more store's state. | ||
* | ||
* @param select Function that fetch stores and return the result | ||
* @param comparator Compare old & new value returned by `select()`. | ||
* @returns React hook that evaluate select() when there is state change event; | ||
*/ | ||
if (manager.devTool) { | ||
var type = action.store.name + "." + (action.name || '<unknown>'); | ||
function createSelector(select, comparator) { | ||
if (comparator === void 0) { | ||
comparator = defaultComparator; | ||
} | ||
if (!autoCommit) { | ||
type += ' (deferred)'; | ||
} | ||
return function useSelector() { | ||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
manager.devTool.log({ | ||
type: type, | ||
args: action.args | ||
}); | ||
} | ||
}; | ||
var manager = useStoreManager(); | ||
var stores = React.useRef([]); | ||
var getCurrentResult = React.useCallback(function () { | ||
// easy debugging | ||
var selectedStores = []; | ||
var result = select.apply(void 0, [function (store) { | ||
selectedStores.push(store); | ||
return manager.getState(store); | ||
}].concat(args)); | ||
stores.current = selectedStores; | ||
return result; // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [manager].concat(args)); // array stores maybe recreated but not its values | ||
// subscribe to store changes | ||
if (isPromise(result)) { | ||
return result.then(handleResult); | ||
} else { | ||
handleResult(result); | ||
return useStoreSubscription({ | ||
manager: manager, | ||
stores: stores, | ||
getCurrentResult: getCurrentResult, | ||
comparator: comparator | ||
}); | ||
}; | ||
} | ||
var storeImplementation; | ||
function createStore(name, initialState) { | ||
{ | ||
var _ref, _stack, _stack$split, _stack$split$; | ||
storeImplementation = storeImplementation || new Map(); | ||
if (storeImplementation.has(name)) { | ||
throw new Error("Store with name \"" + name + "\" has already been declared " + storeImplementation.get(name)); | ||
} | ||
var location = (_ref = (_stack = new Error().stack) === null || _stack === void 0 ? void 0 : (_stack$split = _stack.split('\n')) === null || _stack$split === void 0 ? void 0 : (_stack$split$ = _stack$split[2]) === null || _stack$split$ === void 0 ? void 0 : _stack$split$.trim()) !== null && _ref !== void 0 ? _ref : 'at unknown location'; | ||
storeImplementation.set(name, location); | ||
} | ||
return { | ||
name: name, | ||
initState: isInitiatorFunction(initialState) ? initialState : function () { | ||
return initialState; | ||
} | ||
}; | ||
} | ||
function isPromise(p) { | ||
return typeof p === 'object' && 'then' in p && typeof p.then === 'function'; | ||
function isInitiatorFunction(x) { | ||
return typeof x === 'function'; | ||
} | ||
@@ -307,40 +442,52 @@ | ||
var manager = useStoreManager(); | ||
return React.useMemo(function () { | ||
return createDispatcher(manager); | ||
}, [manager]); | ||
return manager.dispatcher; | ||
} | ||
/** | ||
* Use action with static arguments. If you need dynamic argument, use {@link useDispatcher} instead, | ||
* or {@link useActionCallback} if you want to use it as component callback paramater | ||
* | ||
* @param action Action that will be called | ||
* @param args Static action argument | ||
*/ | ||
function useAction(action) { | ||
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
args[_key - 1] = arguments[_key]; | ||
} | ||
var dispatch = useDispatcher(); | ||
return React.useMemo(function () { | ||
if ('store' in action) { | ||
return function () { | ||
return dispatch(action); | ||
}; | ||
} else { | ||
return function () { | ||
return dispatch(action.apply(void 0, arguments)); | ||
}; | ||
} | ||
}, [dispatch, action]); | ||
} | ||
return React.useCallback(function () { | ||
dispatch.apply(void 0, [action].concat(args)); | ||
}, // eslint-disable-next-line react-hooks/exhaustive-deps | ||
[dispatch, action].concat(args) // remapArg maybe recreated, use deps instead | ||
); | ||
} // real implementation | ||
function isStateUpdaterFunction(updater) { | ||
return typeof updater === 'function'; | ||
} | ||
function useActionCallback(action, remapArg, deps) { | ||
if (deps === void 0) { | ||
deps = []; | ||
} | ||
function createStore(name, initialState) { | ||
return { | ||
name: name, | ||
initialState: initialState | ||
}; | ||
} | ||
var dispatch = useDispatcher(); | ||
return React.useCallback(function (event) { | ||
var args = remapArg ? remapArg(event) : []; | ||
function combineStore() { | ||
for (var _len = arguments.length, stores = new Array(_len), _key = 0; _key < _len; _key++) { | ||
stores[_key] = arguments[_key]; | ||
} | ||
{ | ||
// -1 is for dispatch context | ||
var neededArgLength = action.fn.length - 1; | ||
return stores; | ||
if (neededArgLength > args.length) { | ||
var fullActionName = action.store.name + '.' + action.name || '<unknown>'; | ||
throw new Error("Action " + fullActionName + " needs " + neededArgLength + " arguments, but only get " + args.length); | ||
} | ||
} | ||
dispatch.apply(void 0, [action].concat(args)); | ||
}, // eslint-disable-next-line react-hooks/exhaustive-deps | ||
[dispatch, action].concat(deps) // remapArg maybe recreated, use deps instead | ||
); | ||
} | ||
function useStore(inputStore, mapState, comparator, deps) { | ||
function useStore(store, mapState, comparator, deps) { | ||
if (mapState === void 0) { | ||
@@ -351,3 +498,3 @@ mapState = identityFn; | ||
if (comparator === void 0) { | ||
comparator = defaultComparator(Array.isArray(inputStore)); | ||
comparator = strictComparator; | ||
} | ||
@@ -360,66 +507,19 @@ | ||
// get store manager from context | ||
var manager = useStoreManager(); // normalize parameters, all the logic bellow assume user input multiple stores | ||
var manager = useStoreManager(); | ||
var stores = React.useRef([store]); // fetch state from store manager, and transform the result. | ||
var stores = Array.isArray(inputStore) ? inputStore : [inputStore]; // mapState maybe has different reference, use `deps` argument if it depends on other variable | ||
var normalizedMapState = React.useCallback(function (states) { | ||
return Array.isArray(inputStore) ? mapState(states) : mapState(states[0]); | ||
}, // eslint-disable-next-line react-hooks/exhaustive-deps | ||
[Array.isArray(inputStore)].concat(deps)); // fetch state from store manager, and transform the result. | ||
var getCurrentResult = React.useCallback(function () { | ||
// easy debugging | ||
var states = getOrFillStateArray(manager, stores); | ||
var result = normalizedMapState(states); | ||
var states = manager.getState(store); | ||
var result = mapState(states); | ||
return result; // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [manager, normalizedMapState].concat(stores)); // array stores maybe recreated but not its values | ||
// use ref to store current result and initialize the value. | ||
// the type can not be `Return` since it may be undefined | ||
// we need to make sure that current.value === undefined only | ||
// if it isn't initialized | ||
}, [manager, store].concat(deps)); // array stores maybe recreated but not its values | ||
// subscribe to store changes | ||
var value = React.useRef(); | ||
if (value.current === undefined) { | ||
value.current = { | ||
result: getCurrentResult() | ||
}; | ||
} // tell react to rerender this component | ||
// should be called after updating `value.current` | ||
var _useState = React.useState({}), | ||
forceRerender = _useState[1]; // compare value.current with value from store manager | ||
// if it changed, update the value and force rerender | ||
var updateResultAndForceRender = React.useCallback(function () { | ||
var result = getCurrentResult(); | ||
var equal = comparator(value.current.result, result); | ||
if (!equal) { | ||
value.current = { | ||
result: result | ||
}; | ||
forceRerender({}); | ||
} // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [value, getCurrentResult, forceRerender]); // comparator must be pure function | ||
React.useEffect(function () { | ||
// maybe the state was changed while this component is being rendered | ||
updateResultAndForceRender(); // subscribe to all stores change | ||
// and return function to unscribe them | ||
var unsubscribeFunctions = stores.map(function (store) { | ||
return manager.subscribe(store, updateResultAndForceRender); | ||
}); | ||
return function () { | ||
return unsubscribeFunctions.forEach(function (fn) { | ||
return fn(); | ||
}); | ||
}; // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [manager, updateResultAndForceRender].concat(stores)); // but not its values | ||
return value.current.result; | ||
return useStoreSubscription({ | ||
manager: manager, | ||
stores: stores, | ||
getCurrentResult: getCurrentResult, | ||
comparator: comparator | ||
}); | ||
} | ||
@@ -431,19 +531,14 @@ | ||
function defaultComparator(isArray) { | ||
return isArray ? arrayComparator : strictComparator; | ||
function strictComparator(a, b) { | ||
return a === b; | ||
} | ||
function getOrFillStateArray(manager, stores) { | ||
return stores.map(function (store) { | ||
return manager.getState(store); | ||
}); | ||
} | ||
exports.StoreManagerProvider = StoreManagerProvider; | ||
exports.StoreProvider = StoreProvider; | ||
exports.arrayComparator = arrayComparator; | ||
exports.combineStore = combineStore; | ||
exports.createAction = createAction; | ||
exports.createSelector = createSelector; | ||
exports.createStore = createStore; | ||
exports.strictComparator = strictComparator; | ||
exports.createStoreManager = createStoreManager; | ||
exports.useAction = useAction; | ||
exports.useActionCallback = useActionCallback; | ||
exports.useDispatcher = useDispatcher; | ||
@@ -450,0 +545,0 @@ exports.useStore = useStore; |
@@ -1,2 +0,2 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=require("react"),e=require("react-dom");function r(t,e){return t===e}function n(t,e){if(t.length!==e.length)return!1;for(var r in t)if(t[r]!==e[r])return!1;return!0}var o=t.createContext(void 0);function u(){var e=t.useContext(o);if(!e)throw new Error("Not inside provider");return e}function a(){var e=u();return t.useMemo((function(){return function t(e,r){return function(n){var o,u,a=!r,i=n.store.name,c=null!==(o=null==r?void 0:r.changesMap)&&void 0!==o?o:new Map,f=r?r.commit:function(){e.commit(c),c.clear()},s=function(){return c.get(i)||e.getState(n.store)},l=function(t,e){void 0===e&&(e=!1);var r=function(t){return"function"==typeof t}(t)?t(s()):t;c.set(i,r),e&&f()},v=n.call({state:s(),getState:s,setState:l,commit:f,disableAutoCommit:function(){a=!1},getStore:function(t){return c.get(t.name)||e.getState(t)},dispatch:t(e,{changesMap:c,commit:f})}),d=function(t){if(void 0!==t&&l(t),a&&f(),e.devTool){var r=n.store.name+"."+(n.name||"<unknown>");a||(r+=" (deferred)"),e.devTool.log({type:r,args:n.args})}};if("object"==typeof(u=v)&&"then"in u&&"function"==typeof u.then)return v.then(d);d(v)}}(e)}),[e])}function i(t){return t}exports.StoreProvider=function(r){var n=r.initialStates,u=r.enableDevTool,a=function(t,e){if(null==t)return{};var r,n,o={},u=Object.keys(t);for(n=0;n<u.length;n++)e.indexOf(r=u[n])>=0||(o[r]=t[r]);return o}(r,["initialStates","enableDevTool"]),i=t.useMemo((function(){return function(t,r){var n=t||new Map,o=new Map,u={subscribe:function(t,e){var r=t.name;o.has(r)||o.set(r,new Set);var n=o.get(r);return n.add(e),function(){return n.delete(e)}},getState:function(t){var e=t.name;if(n.has(e))return n.get(e);var r="function"==typeof t.initialState?t.initialState():t.initialState;return n.set(e,r),r},commit:function(t){var r=new Set;t.forEach((function(t,e){n.set(e,t);var u=o.get(e);u&&u.forEach((function(t){return r.add(t)}))})),r.size>0&&e.unstable_batchedUpdates((function(t){return t.forEach((function(t){return t()}))}),r)}};return r&&(u.devTool=void 0),u}(n,u)}),[n,u]);return t.useEffect((function(){return function(){var t;return null===(t=i.devTool)||void 0===t?void 0:t.disconnect()}})),t.createElement(o.Provider,Object.assign({},a,{value:i}))},exports.arrayComparator=n,exports.combineStore=function(){for(var t=arguments.length,e=new Array(t),r=0;r<t;r++)e[r]=arguments[r];return e},exports.createAction=function(t,e,r){return function(){for(var n=arguments.length,o=new Array(n),u=0;u<n;u++)o[u]=arguments[u];return{store:t,call:function(t){return e.apply(void 0,[t].concat(o))},name:r,args:o}}},exports.createStore=function(t,e){return{name:t,initialState:e}},exports.strictComparator=r,exports.useAction=function(e){var r=a();return t.useMemo((function(){return"store"in e?function(){return r(e)}:function(){return r(e.apply(void 0,arguments))}}),[r,e])},exports.useDispatcher=a,exports.useStore=function(e,o,a,c){var f;void 0===o&&(o=i),void 0===a&&(f=Array.isArray(e),a=f?n:r),void 0===c&&(c=[]);var s=u(),l=Array.isArray(e)?e:[e],v=t.useCallback((function(t){return Array.isArray(e)?o(t):o(t[0])}),[Array.isArray(e)].concat(c)),d=t.useCallback((function(){var t=function(t,e){return e.map((function(e){return t.getState(e)}))}(s,l);return v(t)}),[s,v].concat(l)),p=t.useRef();void 0===p.current&&(p.current={result:d()});var m=t.useState({})[1],g=t.useCallback((function(){var t=d();a(p.current.result,t)||(p.current={result:t},m({}))}),[p,d,m]);return t.useEffect((function(){g();var t=l.map((function(t){return s.subscribe(t,g)}));return function(){return t.forEach((function(t){return t()}))}}),[s,g].concat(l)),p.current.result},exports.useStoreManager=u; | ||
"use strict";function t(t){return t&&"object"==typeof t&&"default"in t?t.default:t}Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react-dom"),r=require("react"),n=t(r),o=t(require("fast-deep-equal"));function u(t){return"object"==typeof t&&"then"in t&&"function"==typeof t.then}function a(t){return"function"==typeof t}function c(t,r){var n=t||new Map,o=new Map,c={subscribe:function(t,e){var r=t.name;o.has(r)||o.set(r,new Set);var n=o.get(r);return n.add(e),function(){return n.delete(e)}},getState:function(t){var e=t.name;if(n.has(e))return n.get(e);var r=t.initState();return n.set(e,r),r},commit:function(t){var r=new Set;t.forEach((function(t,e){n.set(e,t);var u=o.get(e);u&&u.forEach((function(t){return r.add(t)}))})),r.size>0&&e.unstable_batchedUpdates((function(t){return t.forEach((function(t){return t()}))}),r)},dispatcher:void 0};return c.dispatcher=function(t){return function(e){var r=new Map,n=function(){r.size>0&&t.commit(r),r.clear()},o=function(t){for(var e=arguments.length,r=new Array(e>1?e-1:0),n=1;n<e;n++)r[n-1]=arguments[n];return i(t,r,!1)},c=!1;function i(e,i,f){var s=f,v=e.store.name,l=function(){return r.get(v)||t.getState(e.store)},p=function(t,e){void 0===e&&(e=!1);var o=a(t)?t(l()):t;r.set(v,o),e&&n()},d={state:l(),getState:l,setState:p,mergeState:function(t,e){var r=l();if("object"!=typeof r||Array.isArray(r))throw new Error("Merge state only available for object based state");p(Object.assign(Object.create(null),r,t),e)},commit:n,disableAutoCommit:function(){s=!1},getStore:function(e){return r.get(e.name)||t.getState(e)},dispatch:o},g=e.fn.apply(e,[d].concat(i)),m=function(t){void 0!==t&&p(t),(s||c)&&n(),f&&(c=!0)};return u(g)?g.then(m):m(g)}for(var f=arguments.length,s=new Array(f>1?f-1:0),v=1;v<f;v++)s[v-1]=arguments[v];return i(e,s,!0)}}(c),r&&(c.devTool=void 0),c}var i=n.createContext(void 0),f=i.Provider;function s(){var t=r.useContext(i);if(!t)throw new Error("You must wrap the component with <StoreProvider>");return t}function v(t){var e=t.manager,n=t.stores,o=t.getCurrentResult,u=t.comparator,a=r.useRef();void 0===a.current&&(a.current={result:o()});var c=r.useState({})[1],i=r.useCallback((function(){var t=o();u(a.current.result,t)||(a.current={result:t},c({}))}),[a,o,c]);return r.useEffect((function(){i();var t=n.current.map((function(t){return e.subscribe(t,i)}));return function(){return t.forEach((function(t){return t()}))}}),[e,i].concat(n.current)),a.current.result}function l(){return s().dispatcher}function p(t){return t}function d(t,e){return t===e}exports.StoreManagerProvider=f,exports.StoreProvider=function(t){var e=t.initialStates,o=t.enableDevTool,u=function(t,e){if(null==t)return{};var r,n,o={},u=Object.keys(t);for(n=0;n<u.length;n++)e.indexOf(r=u[n])>=0||(o[r]=t[r]);return o}(t,["initialStates","enableDevTool"]),a=r.useMemo((function(){return c(e,o)}),[e,o]);return r.useEffect((function(){return function(){var t;return null===(t=a.devTool)||void 0===t?void 0:t.disconnect()}}),[a]),n.createElement(f,Object.assign({},u,{value:a}))},exports.createAction=function(t,e,r){return{store:t,fn:e,name:r}},exports.createSelector=function(t,e){return void 0===e&&(e=o),function(){for(var n=arguments.length,o=new Array(n),u=0;u<n;u++)o[u]=arguments[u];var a=s(),c=r.useRef([]),i=r.useCallback((function(){var e=[],r=t.apply(void 0,[function(t){return e.push(t),a.getState(t)}].concat(o));return c.current=e,r}),[a].concat(o));return v({manager:a,stores:c,getCurrentResult:i,comparator:e})}},exports.createStore=function(t,e){return{name:t,initState:(r=e,"function"==typeof r?e:function(){return e})};var r},exports.createStoreManager=c,exports.useAction=function(t){for(var e=arguments.length,n=new Array(e>1?e-1:0),o=1;o<e;o++)n[o-1]=arguments[o];var u=l();return r.useCallback((function(){u.apply(void 0,[t].concat(n))}),[u,t].concat(n))},exports.useActionCallback=function(t,e,n){void 0===n&&(n=[]);var o=l();return r.useCallback((function(r){var n=e?e(r):[];o.apply(void 0,[t].concat(n))}),[o,t].concat(n))},exports.useDispatcher=l,exports.useStore=function(t,e,n,o){void 0===e&&(e=p),void 0===n&&(n=d),void 0===o&&(o=[]);var u=s(),a=r.useRef([t]),c=r.useCallback((function(){var r=u.getState(t);return e(r)}),[u,t].concat(o));return v({manager:u,stores:a,getCurrentResult:c,comparator:n})},exports.useStoreManager=s; | ||
//# sourceMappingURL=el-state.cjs.production.min.js.map |
@@ -1,51 +0,14 @@ | ||
import { useMemo, useEffect, createElement, useContext, createContext, useCallback, useRef, useState } from 'react'; | ||
import { unstable_batchedUpdates } from 'react-dom'; | ||
import React, { useMemo, useEffect, useContext, useRef, useState, useCallback } from 'react'; | ||
import defaultComparator from 'fast-deep-equal'; | ||
function createAction(store, action, name) { | ||
return function () { | ||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
return { | ||
store: store, | ||
call: function call(ctx) { | ||
return action.apply(void 0, [ctx].concat(args)); | ||
}, | ||
name: name, | ||
args: args | ||
}; | ||
// real implementation | ||
function createAction(store, fn, name) { | ||
return { | ||
store: store, | ||
fn: fn, | ||
name: name | ||
}; | ||
} | ||
function strictComparator(a, b) { | ||
return a === b; | ||
} | ||
function arrayComparator(a, b) { | ||
if (a.length !== b.length) { | ||
return false; | ||
} | ||
for (var i in a) { | ||
if (a[i] !== b[i]) return false; | ||
} | ||
return true; | ||
} | ||
function _objectWithoutPropertiesLoose(source, excluded) { | ||
if (source == null) return {}; | ||
var target = {}; | ||
var sourceKeys = Object.keys(source); | ||
var key, i; | ||
for (i = 0; i < sourceKeys.length; i++) { | ||
key = sourceKeys[i]; | ||
if (excluded.indexOf(key) >= 0) continue; | ||
target[key] = source[key]; | ||
} | ||
return target; | ||
} | ||
function initDevTool(states, subscriptions) { | ||
@@ -118,7 +81,118 @@ if (process.env.NODE_ENV === 'production') { | ||
var Context = | ||
/*#__PURE__*/ | ||
createContext(undefined); | ||
function createDispatcher(manager) { | ||
return function (action) { | ||
var changesMap = new Map(); | ||
function createManager(initialStates, enableDevTool) { | ||
var commit = function commit() { | ||
changesMap.size > 0 && manager.commit(changesMap); | ||
changesMap.clear(); | ||
}; // create child dispatcher once | ||
var childDispatcher = function childDispatcher(childAction) { | ||
for (var _len2 = arguments.length, childArgs = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { | ||
childArgs[_key2 - 1] = arguments[_key2]; | ||
} | ||
return dispatch(childAction, childArgs, false); | ||
}; | ||
var rootDone = false; | ||
function dispatch(childAction, childArgs, isRoot) { | ||
var autoCommit = isRoot; | ||
var storeName = childAction.store.name; | ||
var getState = function getState() { | ||
return changesMap.get(storeName) || manager.getState(childAction.store); | ||
}; | ||
var setState = function setState(updater, forceCommit) { | ||
if (forceCommit === void 0) { | ||
forceCommit = false; | ||
} | ||
var state = isStateUpdaterFunction(updater) ? updater(getState()) : updater; | ||
changesMap.set(storeName, state); | ||
if (forceCommit) { | ||
commit(); | ||
} | ||
}; | ||
var ctx = { | ||
state: getState(), | ||
getState: getState, | ||
setState: setState, | ||
mergeState: function mergeState(partialState, forceCommit) { | ||
var state = getState(); | ||
if (typeof state !== 'object' || Array.isArray(state)) { | ||
throw new Error('Merge state only available for object based state'); | ||
} | ||
setState(Object.assign(Object.create(null), state, partialState), forceCommit); | ||
}, | ||
commit: commit, | ||
disableAutoCommit: function disableAutoCommit() { | ||
autoCommit = false; | ||
}, | ||
getStore: function getStore(otherStore) { | ||
return changesMap.get(otherStore.name) || manager.getState(otherStore); | ||
}, | ||
dispatch: childDispatcher | ||
}; | ||
var result = childAction.fn.apply(childAction, [ctx].concat(childArgs)); | ||
var handleResult = function handleResult(newState) { | ||
if (newState !== undefined) { | ||
setState(newState); | ||
} | ||
if (autoCommit || rootDone) { | ||
commit(); | ||
} | ||
if (process.env.NODE_ENV !== 'production' && manager.devTool) { | ||
var type = childAction.store.name + "." + (childAction.name || '<unknown>'); | ||
if (!autoCommit) { | ||
type += ' (deferred)'; | ||
} | ||
manager.devTool.log({ | ||
type: type, | ||
args: childArgs | ||
}); | ||
} | ||
if (isRoot) { | ||
rootDone = true; | ||
} | ||
}; | ||
if (isPromise(result)) { | ||
return result.then(handleResult); | ||
} else { | ||
return handleResult(result); | ||
} | ||
} | ||
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
args[_key - 1] = arguments[_key]; | ||
} | ||
return dispatch(action, args, true); | ||
}; | ||
} | ||
function isPromise(p) { | ||
return typeof p === 'object' && 'then' in p && typeof p.then === 'function'; | ||
} | ||
function isStateUpdaterFunction(updater) { | ||
return typeof updater === 'function'; | ||
} | ||
function createStoreManager(initialStates, enableDevTool) { | ||
var states = initialStates || new Map(); | ||
@@ -146,3 +220,3 @@ var subscriptions = new Map(); | ||
} else { | ||
var state = isInitiatorFunction(store.initialState) ? store.initialState() : store.initialState; | ||
var state = store.initState(); | ||
states.set(name, state); | ||
@@ -172,4 +246,6 @@ return state; | ||
} | ||
} | ||
}, | ||
dispatcher: undefined | ||
}; | ||
manager.dispatcher = createDispatcher(manager); | ||
@@ -183,2 +259,21 @@ if (enableDevTool) { | ||
function _objectWithoutPropertiesLoose(source, excluded) { | ||
if (source == null) return {}; | ||
var target = {}; | ||
var sourceKeys = Object.keys(source); | ||
var key, i; | ||
for (i = 0; i < sourceKeys.length; i++) { | ||
key = sourceKeys[i]; | ||
if (excluded.indexOf(key) >= 0) continue; | ||
target[key] = source[key]; | ||
} | ||
return target; | ||
} | ||
var Context = | ||
/*#__PURE__*/ | ||
React.createContext(undefined); | ||
var StoreManagerProvider = Context.Provider; | ||
var StoreProvider = function StoreProvider(_ref) { | ||
@@ -190,3 +285,3 @@ var initialStates = _ref.initialStates, | ||
var manager = useMemo(function () { | ||
return createManager(initialStates, enableDevTool); | ||
return createStoreManager(initialStates, enableDevTool); | ||
}, [initialStates, enableDevTool]); | ||
@@ -199,4 +294,4 @@ useEffect(function () { | ||
}; | ||
}); | ||
return createElement(Context.Provider, Object.assign({}, props, { | ||
}, [manager]); | ||
return React.createElement(StoreManagerProvider, Object.assign({}, props, { | ||
value: manager | ||
@@ -209,3 +304,3 @@ })); | ||
if (!ctx) { | ||
throw new Error('Not inside provider'); | ||
throw new Error('You must wrap the component with <StoreProvider>'); | ||
} | ||
@@ -216,86 +311,123 @@ | ||
function isInitiatorFunction(x) { | ||
return typeof x === 'function'; | ||
} | ||
function useStoreSubscription(_ref) { | ||
var manager = _ref.manager, | ||
stores = _ref.stores, | ||
getCurrentResult = _ref.getCurrentResult, | ||
comparator = _ref.comparator; | ||
// use ref to store current result and initialize the value. | ||
// the type can not be `Return` since it may be undefined | ||
// we need to make sure that current.value === undefined only | ||
// if it isn't initialized | ||
var value = useRef(); | ||
function createDispatcher(manager, root) { | ||
return function (action) { | ||
var _ref; | ||
if (value.current === undefined) { | ||
value.current = { | ||
result: getCurrentResult() | ||
}; | ||
} // tell react to rerender this component | ||
// should be called after updating `value.current` | ||
var autoCommit = !root; // root dispatcher | ||
var storeName = action.store.name; | ||
var changesMap = (_ref = root === null || root === void 0 ? void 0 : root.changesMap) !== null && _ref !== void 0 ? _ref : new Map(); | ||
var commit = root ? root.commit : function () { | ||
manager.commit(changesMap); | ||
changesMap.clear(); | ||
}; | ||
var _useState = useState({}), | ||
forceRerender = _useState[1]; // compare value.current with value from store manager | ||
// if it changed, update the value and force rerender | ||
var getState = function getState() { | ||
return changesMap.get(storeName) || manager.getState(action.store); | ||
}; | ||
var setState = function setState(updater, forceCommit) { | ||
if (forceCommit === void 0) { | ||
forceCommit = false; | ||
} | ||
var updateResultAndForceRender = useCallback(function () { | ||
var result = getCurrentResult(); | ||
var equal = comparator(value.current.result, result); | ||
var state = isStateUpdaterFunction(updater) ? updater(getState()) : updater; | ||
changesMap.set(storeName, state); | ||
if (!equal) { | ||
value.current = { | ||
result: result | ||
}; | ||
forceRerender({}); | ||
} // eslint-disable-next-line react-hooks/exhaustive-deps | ||
if (forceCommit) { | ||
commit(); | ||
} | ||
}; | ||
}, [value, getCurrentResult, forceRerender]); // comparator must be pure function | ||
var result = action.call({ | ||
state: getState(), | ||
getState: getState, | ||
setState: setState, | ||
commit: commit, | ||
disableAutoCommit: function disableAutoCommit() { | ||
autoCommit = false; | ||
}, | ||
getStore: function getStore(otherStore) { | ||
return changesMap.get(otherStore.name) || manager.getState(otherStore); | ||
}, | ||
dispatch: createDispatcher(manager, { | ||
changesMap: changesMap, | ||
commit: commit | ||
}) | ||
useEffect(function () { | ||
// maybe the state was changed while this component is being rendered | ||
updateResultAndForceRender(); // subscribe to all stores change | ||
// and return function to unscribe them | ||
var unsubscribeFunctions = stores.current.map(function (store) { | ||
return manager.subscribe(store, updateResultAndForceRender); | ||
}); | ||
return function () { | ||
return unsubscribeFunctions.forEach(function (fn) { | ||
return fn(); | ||
}); | ||
}; // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [manager, updateResultAndForceRender].concat(stores.current)); // but not its values | ||
var handleResult = function handleResult(newState) { | ||
if (newState !== undefined) { | ||
setState(newState); | ||
} | ||
return value.current.result; | ||
} | ||
if (autoCommit) { | ||
commit(); | ||
} | ||
/** | ||
* Create react hook to use one or more store's state. | ||
* | ||
* @param select Function that fetch stores and return the result | ||
* @param comparator Compare old & new value returned by `select()`. | ||
* @returns React hook that evaluate select() when there is state change event; | ||
*/ | ||
if (manager.devTool) { | ||
var type = action.store.name + "." + (action.name || '<unknown>'); | ||
function createSelector(select, comparator) { | ||
if (comparator === void 0) { | ||
comparator = defaultComparator; | ||
} | ||
if (!autoCommit) { | ||
type += ' (deferred)'; | ||
} | ||
return function useSelector() { | ||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
manager.devTool.log({ | ||
type: type, | ||
args: action.args | ||
}); | ||
} | ||
}; | ||
var manager = useStoreManager(); | ||
var stores = useRef([]); | ||
var getCurrentResult = useCallback(function () { | ||
// easy debugging | ||
var selectedStores = []; | ||
var result = select.apply(void 0, [function (store) { | ||
selectedStores.push(store); | ||
return manager.getState(store); | ||
}].concat(args)); | ||
stores.current = selectedStores; | ||
return result; // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [manager].concat(args)); // array stores maybe recreated but not its values | ||
// subscribe to store changes | ||
if (isPromise(result)) { | ||
return result.then(handleResult); | ||
} else { | ||
handleResult(result); | ||
return useStoreSubscription({ | ||
manager: manager, | ||
stores: stores, | ||
getCurrentResult: getCurrentResult, | ||
comparator: comparator | ||
}); | ||
}; | ||
} | ||
var storeImplementation; | ||
function createStore(name, initialState) { | ||
if (process.env.NODE_ENV !== 'production') { | ||
var _ref, _stack, _stack$split, _stack$split$; | ||
storeImplementation = storeImplementation || new Map(); | ||
if (storeImplementation.has(name)) { | ||
throw new Error("Store with name \"" + name + "\" has already been declared " + storeImplementation.get(name)); | ||
} | ||
var location = (_ref = (_stack = new Error().stack) === null || _stack === void 0 ? void 0 : (_stack$split = _stack.split('\n')) === null || _stack$split === void 0 ? void 0 : (_stack$split$ = _stack$split[2]) === null || _stack$split$ === void 0 ? void 0 : _stack$split$.trim()) !== null && _ref !== void 0 ? _ref : 'at unknown location'; | ||
storeImplementation.set(name, location); | ||
} | ||
return { | ||
name: name, | ||
initState: isInitiatorFunction(initialState) ? initialState : function () { | ||
return initialState; | ||
} | ||
}; | ||
} | ||
function isPromise(p) { | ||
return typeof p === 'object' && 'then' in p && typeof p.then === 'function'; | ||
function isInitiatorFunction(x) { | ||
return typeof x === 'function'; | ||
} | ||
@@ -305,40 +437,52 @@ | ||
var manager = useStoreManager(); | ||
return useMemo(function () { | ||
return createDispatcher(manager); | ||
}, [manager]); | ||
return manager.dispatcher; | ||
} | ||
/** | ||
* Use action with static arguments. If you need dynamic argument, use {@link useDispatcher} instead, | ||
* or {@link useActionCallback} if you want to use it as component callback paramater | ||
* | ||
* @param action Action that will be called | ||
* @param args Static action argument | ||
*/ | ||
function useAction(action) { | ||
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { | ||
args[_key - 1] = arguments[_key]; | ||
} | ||
var dispatch = useDispatcher(); | ||
return useMemo(function () { | ||
if ('store' in action) { | ||
return function () { | ||
return dispatch(action); | ||
}; | ||
} else { | ||
return function () { | ||
return dispatch(action.apply(void 0, arguments)); | ||
}; | ||
} | ||
}, [dispatch, action]); | ||
} | ||
return useCallback(function () { | ||
dispatch.apply(void 0, [action].concat(args)); | ||
}, // eslint-disable-next-line react-hooks/exhaustive-deps | ||
[dispatch, action].concat(args) // remapArg maybe recreated, use deps instead | ||
); | ||
} // real implementation | ||
function isStateUpdaterFunction(updater) { | ||
return typeof updater === 'function'; | ||
} | ||
function useActionCallback(action, remapArg, deps) { | ||
if (deps === void 0) { | ||
deps = []; | ||
} | ||
function createStore(name, initialState) { | ||
return { | ||
name: name, | ||
initialState: initialState | ||
}; | ||
} | ||
var dispatch = useDispatcher(); | ||
return useCallback(function (event) { | ||
var args = remapArg ? remapArg(event) : []; | ||
function combineStore() { | ||
for (var _len = arguments.length, stores = new Array(_len), _key = 0; _key < _len; _key++) { | ||
stores[_key] = arguments[_key]; | ||
} | ||
if (process.env.NODE_ENV !== 'production') { | ||
// -1 is for dispatch context | ||
var neededArgLength = action.fn.length - 1; | ||
return stores; | ||
if (neededArgLength > args.length) { | ||
var fullActionName = action.store.name + '.' + action.name || '<unknown>'; | ||
throw new Error("Action " + fullActionName + " needs " + neededArgLength + " arguments, but only get " + args.length); | ||
} | ||
} | ||
dispatch.apply(void 0, [action].concat(args)); | ||
}, // eslint-disable-next-line react-hooks/exhaustive-deps | ||
[dispatch, action].concat(deps) // remapArg maybe recreated, use deps instead | ||
); | ||
} | ||
function useStore(inputStore, mapState, comparator, deps) { | ||
function useStore(store, mapState, comparator, deps) { | ||
if (mapState === void 0) { | ||
@@ -349,3 +493,3 @@ mapState = identityFn; | ||
if (comparator === void 0) { | ||
comparator = defaultComparator(Array.isArray(inputStore)); | ||
comparator = strictComparator; | ||
} | ||
@@ -358,66 +502,19 @@ | ||
// get store manager from context | ||
var manager = useStoreManager(); // normalize parameters, all the logic bellow assume user input multiple stores | ||
var manager = useStoreManager(); | ||
var stores = useRef([store]); // fetch state from store manager, and transform the result. | ||
var stores = Array.isArray(inputStore) ? inputStore : [inputStore]; // mapState maybe has different reference, use `deps` argument if it depends on other variable | ||
var normalizedMapState = useCallback(function (states) { | ||
return Array.isArray(inputStore) ? mapState(states) : mapState(states[0]); | ||
}, // eslint-disable-next-line react-hooks/exhaustive-deps | ||
[Array.isArray(inputStore)].concat(deps)); // fetch state from store manager, and transform the result. | ||
var getCurrentResult = useCallback(function () { | ||
// easy debugging | ||
var states = getOrFillStateArray(manager, stores); | ||
var result = normalizedMapState(states); | ||
var states = manager.getState(store); | ||
var result = mapState(states); | ||
return result; // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [manager, normalizedMapState].concat(stores)); // array stores maybe recreated but not its values | ||
// use ref to store current result and initialize the value. | ||
// the type can not be `Return` since it may be undefined | ||
// we need to make sure that current.value === undefined only | ||
// if it isn't initialized | ||
}, [manager, store].concat(deps)); // array stores maybe recreated but not its values | ||
// subscribe to store changes | ||
var value = useRef(); | ||
if (value.current === undefined) { | ||
value.current = { | ||
result: getCurrentResult() | ||
}; | ||
} // tell react to rerender this component | ||
// should be called after updating `value.current` | ||
var _useState = useState({}), | ||
forceRerender = _useState[1]; // compare value.current with value from store manager | ||
// if it changed, update the value and force rerender | ||
var updateResultAndForceRender = useCallback(function () { | ||
var result = getCurrentResult(); | ||
var equal = comparator(value.current.result, result); | ||
if (!equal) { | ||
value.current = { | ||
result: result | ||
}; | ||
forceRerender({}); | ||
} // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [value, getCurrentResult, forceRerender]); // comparator must be pure function | ||
useEffect(function () { | ||
// maybe the state was changed while this component is being rendered | ||
updateResultAndForceRender(); // subscribe to all stores change | ||
// and return function to unscribe them | ||
var unsubscribeFunctions = stores.map(function (store) { | ||
return manager.subscribe(store, updateResultAndForceRender); | ||
}); | ||
return function () { | ||
return unsubscribeFunctions.forEach(function (fn) { | ||
return fn(); | ||
}); | ||
}; // eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [manager, updateResultAndForceRender].concat(stores)); // but not its values | ||
return value.current.result; | ||
return useStoreSubscription({ | ||
manager: manager, | ||
stores: stores, | ||
getCurrentResult: getCurrentResult, | ||
comparator: comparator | ||
}); | ||
} | ||
@@ -429,13 +526,7 @@ | ||
function defaultComparator(isArray) { | ||
return isArray ? arrayComparator : strictComparator; | ||
function strictComparator(a, b) { | ||
return a === b; | ||
} | ||
function getOrFillStateArray(manager, stores) { | ||
return stores.map(function (store) { | ||
return manager.getState(store); | ||
}); | ||
} | ||
export { StoreProvider, arrayComparator, combineStore, createAction, createStore, strictComparator, useAction, useDispatcher, useStore, useStoreManager }; | ||
export { StoreManagerProvider, StoreProvider, createAction, createSelector, createStore, createStoreManager, useAction, useActionCallback, useDispatcher, useStore, useStoreManager }; | ||
//# sourceMappingURL=el-state.esm.js.map |
export { createAction } from './action'; | ||
export { arrayComparator, strictComparator } from './comparator'; | ||
export { useAction, useDispatcher } from './dispatcher'; | ||
export { GlobalStates, StoreProvider, useStoreManager } from './provider'; | ||
export { createStoreManager, GlobalStates, StoreManager } from './manager'; | ||
export { StoreManagerProvider, StoreProvider, useStoreManager } from './provider'; | ||
export { createSelector } from './selector'; | ||
export { createStore } from './store'; | ||
export { combineStore, useStore } from './useStore'; | ||
export { useAction, useActionCallback } from './useAction'; | ||
export { useDispatcher } from './useDispathcer'; | ||
export { useStore } from './useStore'; |
@@ -1,13 +0,4 @@ | ||
import * as React from 'react'; | ||
import { DevTool } from './devTool'; | ||
import { Store } from './store'; | ||
export declare type GlobalStates = Map<string, unknown>; | ||
export declare type SubscriptionFn = () => void; | ||
export declare type SubscriptionSet = Set<SubscriptionFn>; | ||
export declare type StoreManager = { | ||
getState<State>(store: Store<State>): State; | ||
commit(states: Map<string, unknown>): void; | ||
subscribe(store: Store<unknown>, cb: SubscriptionFn): () => void; | ||
devTool?: DevTool; | ||
}; | ||
import React from 'react'; | ||
import { GlobalStates, StoreManager } from './manager'; | ||
export declare const StoreManagerProvider: React.Provider<StoreManager | undefined>; | ||
declare type ProviderProps = { | ||
@@ -14,0 +5,0 @@ initialStates?: GlobalStates; |
@@ -5,4 +5,4 @@ export declare type StateInitiator<T> = T | StateInitiatorFunction<T>; | ||
readonly name: string; | ||
readonly initialState: StateInitiator<State>; | ||
readonly initState: StateInitiatorFunction<State>; | ||
}; | ||
export declare function createStore<State>(name: string, initialState: StateInitiator<State>): Store<State>; |
import { Store } from './store'; | ||
export declare type StateComparator<T> = (prev: T, current: T) => boolean; | ||
declare type Stores<States extends any[]> = { | ||
[i in keyof States]: Store<States[i]>; | ||
}; | ||
export declare function combineStore<States extends any[]>(...stores: Stores<States>): Stores<States>; | ||
import { StateComparator } from './useStoreSubscription'; | ||
/** | ||
@@ -28,4 +24,1 @@ * Get state of single store. | ||
export declare function useStore<State, Return = State>(store: Store<State>, mapState?: (state: State) => Return, comparator?: StateComparator<Return>, deps?: any[]): Return; | ||
export declare function useStore<States extends any[]>(stores: Stores<States>): States; | ||
export declare function useStore<States extends any[], Return = States>(stores: Stores<States>, mapState: (state: States) => Return, comparator?: StateComparator<Return>): Return; | ||
export {}; |
{ | ||
"name": "el-state", | ||
"version": "0.2.1", | ||
"version": "0.3.0", | ||
"license": "MIT", | ||
@@ -15,3 +15,4 @@ "homepage": "https://github.com/abihf/el-state#readme", | ||
"files": [ | ||
"dist" | ||
"dist", | ||
"!dist/*.test.d.ts" | ||
], | ||
@@ -21,3 +22,3 @@ "scripts": { | ||
"build": "tsdx build", | ||
"test": "tsdx test --env=jsdom", | ||
"test": "tsdx test --env=jsdom --coverage", | ||
"lint": "tsdx lint src" | ||
@@ -53,3 +54,6 @@ }, | ||
"typescript": "^3.7.5" | ||
}, | ||
"dependencies": { | ||
"fast-deep-equal": "^3.1.1" | ||
} | ||
} |
@@ -1,5 +0,13 @@ | ||
# él-staté | ||
#  él-staté | ||
React state management with hook | ||
Type safe state management for React. | ||
## Features | ||
- Type safe: every state & action can be typed. | ||
- Distributed store: no `combineReducers` to create root reducer. You can select multiple stores in single component. Action can dispatch other actions that belong to different store. | ||
- Reducer-less, full of action. You dispatch action that manipulate the state it self. | ||
- Lazy store initialization: stores will not be initialized until it used on component or action. | ||
- Tree shakable: unneeded actions & stores will not be bundled. | ||
## Usage | ||
@@ -10,2 +18,3 @@ | ||
### Use StoreProvider | ||
```js | ||
@@ -17,6 +26,12 @@ import React from 'react'; | ||
ReactDOM.render(<StoreProvider><App /></StoreProvider>, document.getElementById('root')); | ||
ReactDOM.render( | ||
<StoreProvider> | ||
<App /> | ||
</StoreProvider>, | ||
document.getElementById('root') | ||
); | ||
``` | ||
### Define a store | ||
### Define a store & actions | ||
```ts | ||
@@ -28,25 +43,24 @@ import { createStore, createState } from 'el-state'; | ||
}; | ||
const counterStore = createStore({ | ||
initialState: createState<CounterState>({ counter: 0 }), | ||
actions: { | ||
increase(ctx) { | ||
// ctx.state.counter++; // => compile error: counter is readonly | ||
ctx.setState(state => ({ counter: state.counter + 1 })); | ||
}, | ||
set(ctx, counter: number) { | ||
ctx.setState({ counter }); | ||
}, | ||
reset(ctx) { | ||
this.set(ctx, 0); | ||
}, | ||
}, | ||
}); | ||
// Store | ||
export const counterStore = createStore<CounterState>('counter', { counter: 0 }); | ||
// Update state by merging | ||
export const setCounter = createAction(counterStore, ({ mergeState }, counter: number) => mergeState({ counter })); | ||
// Dispatch other action. You can also dispatch action that belong to different store | ||
export const resetCounter = createAction(counterStore, ({ dispatch }) => dispatch(setCounter, 0)); | ||
// Update state by returning new one. You can't mutate `state` because it's read only. | ||
export const increaseCounter = createAction(counterStore, ({ state }) => ({ ...state, counter: state.counter + 1 })); | ||
``` | ||
### Use the store | ||
```js | ||
const MyComononent: React.FC = () => { | ||
const counter = useStore(counterStore, state => state.counter); | ||
```jsx | ||
function MyComponent() { | ||
const counter: number = useStore(counterStore, state => state.counter); | ||
// use the counter | ||
return <div>{counter}</div>; | ||
} | ||
@@ -56,17 +70,38 @@ ``` | ||
### Call an action | ||
```js | ||
const MyComononent: React.FC = () => { | ||
const increaseCounter = useAction(counterStore.increase); | ||
```jsx | ||
function MyComponent() { | ||
const onIncrease = useAction(increaseCounter); | ||
// use this function as onClick handle | ||
<button onClick={onIncrease}>Increase</button>; | ||
} | ||
``` | ||
### Use dispatcher | ||
```js | ||
const MyComononent: React.FC = () => { | ||
Or | ||
```jsx | ||
function MyComponent() { | ||
const dispatch = useDispatcher(); | ||
const onBtnIncreasePressed = () => dispatch(counterStore.increase()); | ||
const onIncrease = () => dispatch(increaseCounter); | ||
// use this function as onClick handle | ||
} | ||
``` | ||
``` | ||
### Create event handler | ||
```jsx | ||
function MyComponent() { | ||
const onChange = useActionCallback(setCounter, (e: React.ChangeEvent<HTMLInputElement>) => [ | ||
parseInt(e.target.value, 10), // you must parse the value, because first argument of setCounter is number | ||
]); | ||
// use this function as event handler | ||
return <input value={counter} onChange={onChange} />; | ||
} | ||
``` | ||
## Attribution | ||
[Nachos icon by Icons8](https://icons8.com/icon/36KGWdxi97kQ/nachos) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
149630
22
1144
104
3
5
1
+ Addedfast-deep-equal@^3.1.1
+ Addedfast-deep-equal@3.1.3(transitive)