diff --git a/lib/context-reducer.js b/lib/context-reducer.js deleted file mode 100644 index 70b1a52..0000000 --- a/lib/context-reducer.js +++ /dev/null @@ -1,66 +0,0 @@ -import React, { - createContext, - useContext, - useMemo, - useRef, - useReducer -} from 'react' - -function createStore({ reducer = {}, actions = {}, initialState = {} }) { - const context = createContext(initialState) - const reducerFn = - typeof reducer === 'function' ? reducer : makeReducerFn(reducer) - - const StoreProvider = ({ children }) => { - const store = useReducer(reducerFn, initialState) - const storeRef = useRef(store) - const thunks = useMemo(() => createThunks(actions, storeRef), [storeRef]) - return ( - {children} - ) - } - - const useStore = selector => { - const [state, actions] = useContext(context) - return typeof selector === 'function' - ? selector(state, actions) - : [state, actions] - } - - return [StoreProvider, useStore] -} - -export default createStore - -/** Returns a set of actions that are bound to the store */ -function createThunks(actions, storeRef) { - const [state, dispatch] = storeRef.current - const thunks = {} - for (let name of Object.keys(actions)) { - thunks[name] = (...args) => - actions[name](...args)({ - dispatch, - state, - type: camelToSnakeCase(name).toUpperCase() - }) - } - return thunks -} - -function makeReducerFn(reducerObj) { - return (state, action) => { - const { type, ...payload } = action - if (typeof reducerObj[type] === 'undefined') return state - const newState = reducerObj[type](state, payload) - return newState - } -} - -const camelToSnakeCase = str => - str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) - -const [Provider, useStore] = createStore({ reducer: {}, actions: {} }) - -export const MyConponent = () => { - const [state, actions] = useStore() -} diff --git a/lib/context-store.js b/lib/context-store.js new file mode 100644 index 0000000..38ff300 --- /dev/null +++ b/lib/context-store.js @@ -0,0 +1,62 @@ +import React, { createContext, useContext, useMemo, useReducer } from 'react' + +/** Takes objects that fully define a store and turns it into a more potent reducer */ +export const useReducerStore = storeDef => { + const { reducer = {}, thunks = {}, initialState } = storeDef + const reducerFn = useMemo(() => makeReducerFn(reducer), [reducer]) + const [state, dispatch] = useReducer(reducerFn, initialState) + const _thunks = useMemo(() => bindThunks(thunks, dispatch), [ + thunks, + dispatch + ]) + return [state, _thunks] +} + +/** A ReducerStore Factory that uses React.context + * Used to make a Store accessible through multiple components + */ +export const createStore = storeDef => { + const context = createContext(null) + const Provider = ({ children }) => { + const store = useReducerStore(storeDef) + return {children} + } + const useStore = () => useContext(context) + return { Provider, Consumer: context.Consumer, useStore } +} + +/** Turn a reducer definition object to a function + * @param {Object} reducer: Object definition of a reducer: {: (state, payload) => } + * @returns {Function}: Proper reducer + */ +function makeReducerFn(reducer) { + return (state, action) => { + const { type, ...payload } = action + if (typeof reducer[type] === 'undefined') return state + const newState = reducer[type](state, payload) + return newState + } +} + +/** Returns a set of actions that are bound to the store + * @param {Object} actions: {: payload => ({dispatch, type}) => } + */ +function bindThunks(thunks, dispatch) { + const _thunks = {} + for (let name of Object.keys(thunks)) { + const defaultType = camelToSnakeCase(name).toUpperCase() + _thunks[name] = (...args) => + thunks[name](...args)(patchDispatch(dispatch, defaultType)) + } + return _thunks +} + +const camelToSnakeCase = str => + str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) + +/** Returns a dispatch function that fills in a default type if none is given */ +function patchDispatch(dispatch, type) { + return action => { + dispatch({ type, ...action }) + } +}