r-magnet
Version:
A state management library for React
326 lines (306 loc) • 11.4 kB
JavaScript
;
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