Skip to content

Instantly share code, notes, and snippets.

@jquense
Created November 1, 2018 18:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jquense/db418c25a43bdb89ddcb43d34fccd03c to your computer and use it in GitHub Desktop.
Save jquense/db418c25a43bdb89ddcb43d34fccd03c to your computer and use it in GitHub Desktop.
redux react hooks
import React, { useReducer, useContext, useMemo } from 'react'
import get from 'lodash/get'
import invariant from 'invariant'
const INIT = {
type: `INIT${Math.random()
.toString(36)
.substring(7)
.split('')
.join('.')}`,
}
function compose(funcs) {
if (funcs.length === 0) return arg => arg
if (funcs.length === 1) return funcs[0]
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
export function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function')
return (...args) => dispatch(actionCreators(...args))
const boundActionCreators = {}
for (const [key, actionCreator] of Object.entries(actionCreators)) {
if (typeof actionCreator === 'function')
boundActionCreators[key] = (...args) => dispatch(actionCreator(...args))
}
return boundActionCreators
}
export const combineReducers = reducers => {
const entries = Object.entries(reducers)
return (state = {}, action, parentPath = 'root') => {
let hasChanged = false
const nextState = {}
for (let [key, reducer] of entries) {
const path = `${parentPath}.${key}`
const prevStateKey = state[key]
const nextStateKey = reducer(prevStateKey, action, __DEV__ && path)
invariant(
nextStateKey !== undefined,
action.type === INIT.type
? `Could not initialize state for reducer key: '${path}', return an initial state value or null for an empty state`
: `Reducer at '${path}' did not return a state value, use null for an empty state`
)
nextState[key] = nextStateKey
hasChanged = hasChanged || nextStateKey !== prevStateKey
}
return hasChanged ? nextState : state
}
}
const applyMiddleware = (middlewares, dispatch) => {
return compose(middlewares.map(m => m(dispatch)))(dispatch)
}
export default (reducers, initialState = {}, middleware) => {
const StateContext = React.createContext({})
const DispatchContext = React.createContext()
const reducer = combineReducers(reducers)
initialState = reducer(initialState, INIT)
function useStoreDispatch() {
return useContext(DispatchContext)
}
function useBoundActions(actionsToBind) {
const dispatch = useStoreDispatch()
return useMemo(() => bindActionCreators(actionsToBind, dispatch), [
actionsToBind,
])
}
function useStoreState(key) {
const state = useContext(StateContext)
return key ? get(state, key) : state
}
function Provider({ children }) {
const [state, baseDispatch] = useReducer(reducer, initialState)
const dispatch = useMemo(() => applyMiddleware(middleware, baseDispatch), [
baseDispatch,
])
return (
<DispatchContext.Provider value={dispatch}>
<StateContext.Provider value={state}>{children}</StateContext.Provider>
</DispatchContext.Provider>
)
}
return {
useBoundActions,
useStoreDispatch,
useStoreState,
Provider,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment