import React, { useReducer, useContext, useMemo } from 'react' import get from 'lodash/get' import invariant from 'invariant' const INIT = { type: `INIT${Math.random() .toString(36) .substring(7) .split('') .join('.')}`, } function compose(funcs) { if (funcs.length === 0) return arg => arg if (funcs.length === 1) return funcs[0] return funcs.reduce((a, b) => (...args) => a(b(...args))) } export function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') return (...args) => dispatch(actionCreators(...args)) const boundActionCreators = {} for (const [key, actionCreator] of Object.entries(actionCreators)) { if (typeof actionCreator === 'function') boundActionCreators[key] = (...args) => dispatch(actionCreator(...args)) } return boundActionCreators } export const combineReducers = reducers => { const entries = Object.entries(reducers) return (state = {}, action, parentPath = 'root') => { let hasChanged = false const nextState = {} for (let [key, reducer] of entries) { const path = `${parentPath}.${key}` const prevStateKey = state[key] const nextStateKey = reducer(prevStateKey, action, __DEV__ && path) invariant( nextStateKey !== undefined, action.type === INIT.type ? `Could not initialize state for reducer key: '${path}', return an initial state value or null for an empty state` : `Reducer at '${path}' did not return a state value, use null for an empty state` ) nextState[key] = nextStateKey hasChanged = hasChanged || nextStateKey !== prevStateKey } return hasChanged ? nextState : state } } const applyMiddleware = (middlewares, dispatch) => { return compose(middlewares.map(m => m(dispatch)))(dispatch) } export default (reducers, initialState = {}, middleware) => { const StateContext = React.createContext({}) const DispatchContext = React.createContext() const reducer = combineReducers(reducers) initialState = reducer(initialState, INIT) function useStoreDispatch() { return useContext(DispatchContext) } function useBoundActions(actionsToBind) { const dispatch = useStoreDispatch() return useMemo(() => bindActionCreators(actionsToBind, dispatch), [ actionsToBind, ]) } function useStoreState(key) { const state = useContext(StateContext) return key ? get(state, key) : state } function Provider({ children }) { const [state, baseDispatch] = useReducer(reducer, initialState) const dispatch = useMemo(() => applyMiddleware(middleware, baseDispatch), [ baseDispatch, ]) return ( {children} ) } return { useBoundActions, useStoreDispatch, useStoreState, Provider, } }