Create context store
This commit is contained in:
parent
30aa1e95dc
commit
c4d5c35950
@ -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
62
lib/context-store.js
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user