Create context store

This commit is contained in:
Alfred Melch 2020-02-01 12:18:32 +01:00
parent 30aa1e95dc
commit c4d5c35950
2 changed files with 62 additions and 66 deletions

View File

@ -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 (
<context.Provider value={[store[0], thunks]}>{children}</context.Provider>
)
}
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()
}

62
lib/context-store.js Normal file
View File

@ -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 <context.Provider value={store}>{children}</context.Provider>
}
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 })
}
}