Here is an abstraction that I use to create standardized contexts
import React, { createContext, useCallback, useContext, useMemo, useReducer } from 'react'
type initialCtx<T> = {
state: T,
updateState: (payload: Partial<T>) => void
}
function makeUseProvider<T extends Record<string, any>>(initialState: T) {
const Context = createContext<initialCtx<T>>({
state: initialState,
updateState: () => null,
})
const Provider = (Component: React.FC<any>) => {
const useContextProvider = () => {
function reducer<T>(state: T, payload: Partial<T>) {
return {
...state,
...payload,
}
}
const [state, dispatch] = useReducer(reducer, initialState) as [T, initialCtx<T>["updateState"]]
const updateState = useCallback((partialState: Partial<T>) => {
dispatch(partialState)
}, [])
const resetState = useCallback(() => {
dispatch(initialState)
}, [dispatch])
return useMemo(() => ({
state,
updateState,
resetState,
}), [state, updateState, resetState])
}
function ContextHOC<T>(props: T) {
const { updateState, state, resetState } = useContextProvider()
const ctx = {
state,
updateState,
resetState,
}
return (
<Context.Provider value={ctx}>
<Component {...props} />
</Context.Provider>
)
}
return ContextHOC
}
return {
Provider,
useProvider: () => useContext(Context),
}
}
export default makeUseProvider
Then it is used like this. You can import useProvider
to access the data and setter in the locations you need it
const { Provider, useProvider } = makeUseProvider({
someValue: "",
someFunction: () => { },
})
const Component = () => {
const { state, updateState } = useProvider()
return <div />
}
export default Provider(Component)
This function is a factory that abstracts out state management (via useReducer
+ Context
). Invoke makeUseProvider
with your initial state. That will be the initial value and does not need to be redefined anywhere else.
const { Provider, useProvider } = makeUseProvider({
someValue: "",
someFunction: () => { },
})
Provider
is a higher order component. It is the same thing as wrapping a set of components in context. You wrap the topmost component with it and state management will be available in all children in the component hierarchy
const Table = Provider(() => {
return <Row />
})
const Row = () => {
const {state, updateState} = useProvider()
return <div />
}
updateState
accepts a subset of the defined data structure (the initial state of makeUseProvider
) and merges the previous state with the new data.
const [ SomeContext, someValue, setSomeValue ] = useCustomContext(SomeContextWrapper);
where the wrapper has the context and the default, etc. – Orchid