I have code that looks something like this:
SomeContext.ts:
export interface SomeContext {
someValue: string;
someFunction: () => void;
}
export const defaultValue: SomeContext = {
someValue: "",
someFunction: () => {},
};
export const SomeContext = React.createContext<SomeContext>(defaultValue);
SomeComponent.tsx:
function SomeComponent() {
const [someValue, setSomeValue] = useState(defaultValue.someValue);
return (
<SomeContext.Provider value={{ someValue, setSomeValue }}>
{/*...*/}
</SomeContext.Provider>
);
}
The part that bugs me is that I have to use defaultValue
to initialize both the context and the state that will control that context.
Isn’t there a more straightforward way to create a context that is controlled by a state? What I mean by that is, isn’t there a way to single-handedly initialize both the state and the context? What is considered best practice here?
I tried not to give someContext
a default value, but then Typescript (maybe rightfully so) gives a warning.
2
Answers
Here is an abstraction that I use to create standardized contexts
Then it is used like this. You can import
useProvider
to access the data and setter in the locations you need itThis function is a factory that abstracts out state management (via
useReducer
+Context
). InvokemakeUseProvider
with your initial state. That will be the initial value and does not need to be redefined anywhere else.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 hierarchyupdateState
accepts a subset of the defined data structure (the initial state ofmakeUseProvider
) and merges the previous state with the new data.I agree, having to define (and maintain) default state is annoying (especially when there are several state values). I usually take the following approach:
Note: much of this boilerplate can be abstracted into a helper/factory function (much like @Andrew’s
makeUseProvider
) but we found that made it more difficult for developers to debug. Indeed, when you yourself revisit the code in 6-months time, it can be hard to figure out what’s going on. So I like this explicit approach better.