Skip to content

Instantly share code, notes, and snippets.

@mattlockyer
Last active September 27, 2020 23:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattlockyer/5b1c18083029fdc42ec10bd8b9dfa319 to your computer and use it in GitHub Desktop.
Save mattlockyer/5b1c18083029fdc42ec10bd8b9dfa319 to your computer and use it in GitHub Desktop.
React useContext, state, dispatch with thunk for namespaced state management
import React, { useContext, useEffect } from 'react';
import { store } from './state/store';
import { onMount } from './state/test';
const ExampleComponent = () => {
const { state, dispatch, update: updateStore } = useContext(store)
console.log(state)
const update = async () => {
// dispatch thunk wraps function with state, dispatch
const res = await dispatch(initNear())
// true
console.log(res)
}
useEffect(() => {
update()
// update store state directly
updateStore({ test: true })
}, [])
return null
};
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { StateProvider } from './state/store.js';
ReactDOM.render(
<StateProvider>
<App />
</StateProvider>,
document.getElementById('root')
);
/********************************
The following store is intended to be updated by passing in a property name
- state updates are additive / write over values e.g. { ...old, ...new }
- if no prop name is passed, you will replace any values at the root with new state
- a prop name can be nested anywhere in the state tree
- to delete, use undefined or go one level up and replace the parent object
- if you add new json objects, you can update them by prop name in a subsequent dispatch
********************************/
import React, { createContext, useReducer } from 'react'
const initialState = {
mounted: false,
preserved: true,
app: {
mounted: false,
preserved: true,
nested: {
mounted: false,
}
},
}
const store = createContext(initialState)
const { Provider } = store
/********************************
find deeply nested state via dot notation
e.g. app.nav.drawer = { isVisible: true } is what we want by calling
update({ isVisible: true }, 'app.nav.drawer')
********************************/
const updateProp = (newState, newPropState, prop = '') => {
if (prop.length === 0) return
prop = prop.split('.')
const path = Object.keys(newState).find((k) => prop[0] === k)
if (!path) {
console.warn('path not found', path)
}
if (prop.length > 1) {
return updateProp(newState[path], newPropState, prop.slice(1).join('.'))
}
newState[path] = { ...newState[path], ...newPropState}
}
const StateProvider = ({ children }) => {
const [state, dispatch] = useReducer((state, newPropState) => {
const { __prop: prop } = newPropState
delete newPropState.__prop
if (!prop) {
return { ...state, ...newPropState }
}
const newState = { ...state }
updateProp(newState, newPropState, prop)
return newState
}, initialState)
const dispatchWithProp = (newPropState, prop) => {
if (prop) {
newPropState.__prop = prop
}
dispatch(newPropState)
}
const wrappedDispatch = async (fn) => fn(state, dispatchWithProp)
return <Provider value={{ state, dispatch: wrappedDispatch, update: dispatchWithProp }}>{children}</Provider>
}
export { store, StateProvider }
export const onMount = () => async (state, dispatch) => {
/********************************
The following store is intended to be updated by passing in a property name
- state updates are additive / write over values e.g. { ...old, ...new }
- if no prop name is passed, you will replace any values at the root with new state
- a prop name can be nested anywhere in the state tree
- to delete, use undefined or go one level up and replace the parent object
- if you add new json objects, you can update them by prop name in a subsequent dispatch
********************************/
dispatch({ mounted: true })
dispatch({ mounted: true }, 'app')
dispatch({ mounted: true }, 'app.nested')
dispatch({ notInitial: { mounted: false } }, 'app.nested')
dispatch({ mounted: true }, 'app.nested.notInitial')
return true
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment