rsvp-reader/lib/context-store.js

63 lines
2.1 KiB
JavaScript
Raw Normal View History

2020-02-01 12:18:32 +01:00
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)
2020-02-01 14:51:53 +01:00
return React.createElement(context.Provider, { value: store }, children)
2020-02-01 12:18:32 +01:00
}
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: {<type>: (state, payload) => <newState>}
* @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: {<name>: 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 })
}
}