Handling global state in React without Providers and boilerplate
2 min read
ReactJS has a built-in way to handle "global" state - React Context. However, after using many contexts I realized I am typing a lot of boilerplate code and getting to a "Provider hell". I had to nest Provider after Provider in my root App component to ensure that all the children had access to it. For a simple example - let's construct a changable theme context.
First, we need to create the type of the context (this article assumes usage of typescript). Let's have the type be similar to the return value of useState - so
[theme, setTheme]. Then, we need to create the context, and pass it an initial value. The second (setTheme) element of the tuple is unnecessary, but we still have to do it. Then we need to create the provider, which takes children as props and passes them down the tree, while creating the theme and passing it to the
ThemeContext.Provider component. The entire code of that file looks like this.
Then we have to go to a component close to the root of our tree and add the Provider there, next to many other providers (if the project is big enough).
Facing the need of "state-like" contexts, I decided to give it a different solution. I built a createStateHook function which keeps the state in a closure and returns a hook that uses that state and rerenders when the state changes. I used the "magic" of rxjs for this to make the code shorter, but it's entirely possible without using rxjs dependency.
Now we can create the theme in a much easier way. We only need the type of the theme, and to call the createStateHook function with the initial value.
There is no need to add a provider at a higher level. useTheme hook will work anywhere, and when the theme is changed - all the components that use useTheme will be rerendered.
For global values that resemble the shape of "state" (which is a lot of them), this approach is far more concise. We converted code from ~20 lines + a Provider in the root component to 2 lines without a provider.