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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user