UNPKG

r-magnet

Version:

A state management library for React

326 lines (306 loc) 11.4 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var React = require('react'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); //this function copied from react-redux const useIsomorphicLayoutEffect = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined" ? React__default["default"].useLayoutEffect : React__default["default"].useEffect; const MagneticContext = React__default["default"].createContext(null); { MagneticContext.displayName = 'MagnetState'; } function useMagneticContext() { const contextValue = React__default["default"].useContext(MagneticContext); if (!contextValue) { throw new Error('could not find magnetic-state context value; please ensure the component is wrapped in a <Provider>'); } return contextValue; } function createStoreHook(context = MagneticContext) { const useMonoContext = context === MagneticContext ? useMagneticContext : () => React__default["default"].useContext(context); return function useStore() { const store = useMonoContext(); return store; }; } const useStore = createStoreHook(); function subscribeEffect(actionTypes, callback, effectMap) { let key = Number(new Date()).toString() + Math.random(); let notifyCallback = (dispatch, getData, action) => { if (actionTypes.includes(action.type)) { callback(action, getData, dispatch); } }; effectMap.set(key, notifyCallback); return { unsubscribe: () => { effectMap.delete(key); }, }; } /** * A hook that allows you to manage side effects in your component based on the action/s. * And it will automatically unsubscribe when the component unmounts. * @param actions An array of action functions or a single action function - `generated from createAction()`. * @param handlerFn A function that accepts the dispatch, getState and action. */ function useEmEffect(actions, handlerFn) { const store = useStore(); useIsomorphicLayoutEffect(() => { let _actions = Array.isArray(actions) ? actions : [actions]; const sub = subscribeEffect(_actions.map((actionFn) => actionFn._$atype), handlerFn, store.__effectMap); return () => { sub.unsubscribe(); }; }, []); } function is(x, y) { if (x === y) { return x !== 0 || y !== 0 || 1 / x === 1 / y; } else { return x !== x && y !== y; } } function shallowEqual(objA, objB) { if (is(objA, objB)) return true; if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { return false; } const keysA = Object.keys(objA); const keysB = Object.keys(objB); if (keysA.length !== keysB.length) return false; for (let i = 0; i < keysA.length; i++) { if (!Object.prototype.hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { return false; } } return true; } function useSelector(selector = (state) => state, equalityFn = shallowEqual) { const store = useStore(); const [state, setState] = React.useState(selector(store.getState())); let oldState = null; useIsomorphicLayoutEffect(() => { return store.subscribe((mState) => { const newState = selector(mState); if (!equalityFn(newState, oldState)) { setState(newState); oldState = newState; } }); }, [store]); return state; } function useSelectorByActions(acions, selector, equalityFn = shallowEqual) { const store = useStore(); const [selectedState, setState] = React.useState(selector(store.getState())); let oldState = selectedState; useEmEffect(acions, () => { const newState = selector(store.getState()); if (!equalityFn(newState, oldState)) { setState(newState); oldState = newState; } }); return selectedState; } function createAction(type) { //@ts-ignore const fx = (payload) => this.dispatch({ type, payload: payload }); fx._$atype = type; return fx; } function createReducer(store) { /** * A function that accepts an initial state, an object full of reducer * functions, and a "state name", and also it can have an object full of effect handlers, and automatically generates * action creators and action types that correspond to the * reducers and state and effects. * */ return function createReducerHelper(options) { if (!options.name) { throw new Error('`name` is a required option for reducer'); } const reducers = options.reducers || {}; const actions = {}; const effects = options.effects || {}; const actionRegx = new RegExp(`^${options.name}.+(?:request|success|failure)$`, 'i'); function reducer(state = options.initialState, action) { if (typeof reducers[action.type] === 'function') { return reducers[action.type](state, action); } else if (actionRegx.test(action.type)) { return Object.assign(Object.assign({}, state), { [action.key]: action.data }); } return state; } Object.keys(reducers).map((key) => { const mkey = `${options.name}_${key}`; reducers[mkey] = reducers[key]; reducers[key] = undefined; actions[key] = store.createAction(mkey); }); const resolveEffect = (effectKey, state) => (key, apiData) => { const currentData = state()[options.name][key].data; store.dispatch({ type: `${options.name}_${effectKey}_request`, key, data: { loading: true, data: currentData, error: null }, }); Promise.resolve(apiData) .then((data) => store.dispatch({ type: `${options.name}_${effectKey}_success`, key, data: { loading: false, data, error: null }, })) .catch((err) => store.dispatch({ type: `${options.name}_${effectKey}_failure`, key, data: { loading: false, data: currentData, error: err.message ? err.message : err, }, })); }; Object.keys(effects).map((key) => { const handler = effects[key]; actions[key] = (payload) => store.dispatch(() => handler({ payload }, resolveEffect(key, store.getState), store.getState, store.dispatch)); }); store.addReducer(options.name, reducer, options.initialState); return { name: options.name, actions, reducer, }; }; } function debounce(func, timeout = 300) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { //@ts-ignore func.apply(this, args); }, timeout); }; } function createEffectHelper(effectMap) { /** * A function that allows you to manage side effects from outside of the components. * @param actions An array of action functions or a single action function - `generated from createAction()`. * @param handlerFn A function that accepts the dispatch, getState and action. * @returns cleanup function */ return function createEffect(actions, handlerFn) { let _actions = Array.isArray(actions) ? actions : [actions]; return subscribeEffect(_actions.map((actionFn) => actionFn._$atype), handlerFn, effectMap); }; } function onHelper(effectMap) { return function on(...actions) { let _actions = actions.map((actionFn) => actionFn._$atype); return { debounce(milliseconds) { return { effect(handlerFn) { return subscribeEffect(_actions, debounce(handlerFn, milliseconds), effectMap); }, }; }, effect(handlerFn) { return subscribeEffect(_actions, handlerFn, effectMap); }, }; }; } function createStoreHelper(initialState) { const listenerList = new Set(); let state = initialState; const reducers = new Map(); const effectMap = new Map(); function notifyAll(action) { listenerList.forEach((listener) => listener(state, action)); } const obj = { getState() { return state; }, subscribe(listener) { listenerList.add(listener); return () => listenerList.delete(listener); }, dispatch(action) { if (typeof action === 'function') { action(); return; } let stateChanged = false; reducers.forEach((reduce, key) => { const oldState = state[key]; const newState = reduce(oldState, action); if (newState !== oldState) { state[key] = newState; stateChanged = true; } }); if (stateChanged) { state = Object.assign({}, state); notifyAll(action); } effectMap.forEach((cal) => cal(obj.dispatch, obj.getState, action)); }, addReducer(name, reducer, init) { reducers.set(name, reducer); state = Object.assign(Object.assign({}, state), { [name]: init }); notifyAll(); }, dispose() { }, createAction, }; return { getState: obj.getState, dispose: obj.dispose, createAction: obj.createAction, createReducer: createReducer(obj), on: onHelper(effectMap), createEffect: createEffectHelper(effectMap), subscribe: obj.subscribe, __effectMap: effectMap, dispatch: obj.dispatch, addReducer: obj.addReducer, }; } function createStore() { return createStoreHelper({}); } const Provider = ({ store, context, children }) => { useIsomorphicLayoutEffect(() => { return () => { store === null || store === void 0 ? void 0 : store.dispose(); }; }, [store]); const Context = context || MagneticContext; return React__default["default"].createElement(Context.Provider, { value: store }, children); }; exports.Provider = Provider; exports.createStore = createStore; exports.useEmEffect = useEmEffect; exports.useSelector = useSelector; exports.useSelectorByActions = useSelectorByActions; exports.useStore = useStore; //# sourceMappingURL=index.cjs.development.js.map