Step-by-Step evolution of using useContext and useReducer in React/React-Native

FanchenBao
4 min readFeb 13, 2023

Many articles exist online about useContext and useReducer in React/React-Native. So why do we need yet another one on the same topic? The justification is that I want to show a step-by-step evolution of how useContext and useReducer help simplify code and separate state management from UI logic. By the end of this article, the benefit of combining these two hooks would be self-evident.

Sample Task

Suppose we want to implement “use dark mode” feature on a web app, where we have a global checkbox that can set dark mode for three panels. In addition, we want each panel to have its own “use dark mode” checkbox to behave the same as the global checkbox, i.e., when the “use dark mode” checkbox of one panel is checked, all the other panels turn to dark mode as well (this example is adapted from React’s documentation).

Implementation with useState

The most straightforward solution is to use useState, as shown below. The main drawback is that it is not scalable. What if we have many panels, or even panel within panel? We cannot copy and paste theme={theme} setTheme={setTheme} all the time. This problem is also known as props drilling.

Slide the vertical white bar to see the source code.

Implementation with useState + useContext

useContext resolves the scalability problem. It has a simple concept: provide one state such that all components under the state can access its value without having to pass the props around.

Some syntax change is needed (shown below), but it’s not too bad. The benefit is significant, as we no longer have to pass the state and state management function to Panel via props.

However, there is still an issue. We define setTheme, i.e., the logic of how theme is updated, and theme itself in the UI code. What if we accidentally change the default value of theme or the logic of setTheme ? How much cleaner and easier to manage our code it would be if we separate the logic of theme and setTheme from UI code! We will surely appreciate this decoupling if our state gets enormous and complicated.

Slide the vertical white bar to see the source code.

Implementation with useReducer + useContext

To separate state and state management from UI code, we can switch useState with useReducer. useReducer has a straightforward concept: it is essentially the same as useState, except that it makes state update independent of any component (in contrast, useState always has to be tied to a component).

As usual, some syntax change is needed (as shown below). But again, it is not too bad. The benefit is tremendous because the state update logic is completely hidden from the UI. For instance, when UI wants to set the theme to dark, all it does is call setDark, which just says to dispatch an object that contains “SET_DARK” as its type. What does “SET_DARK” actually do in terms of state updates? UI does not know, nor should it care.

With the combination of useReducer and useContext, we completely separate the state and state management from UI. We can take one more step to restructure our app such that the decoupled code live in separate modules.

Slide the vertical white bar to see the source code.

New App Structure

The new app structure adds a context folder. In this folder, we have an actions module defining what actions are allowed for state update; a reducer module that holds the state update logic; a store module that sets up the initial state, context, and provider.

These three modules are considered boilerplate code, but further modification is easy once they are set up. However, the major benefit is that our App module now looks pristine. It does not know anything about state management; all it does is use theme, or call the two black-box functions to update theme. The decoupling of state management and UI code is now complete.

Where does the provider go? It goes to index.js, wrapping around our entire app so that we can access the context anywhere in the app.

Click “Open Sandbox” to see the new app structure

Isn’t this Redux?

Ya, it is Redux-light. The new app structure borrows from Redux, which is clean and easy to manage. Redux itself is a whole other learning curve. I will not pretend I understand it fully, and I recommend that anyone wishing to learn more go for the myriads of articles comparing the workflow of useContext + useReducer with Redux. I will say, however, that for an app with a simple state, useContext + useReducer is sufficient. How can we tell a state is simple? If console.log is sufficient for all your debugging needs, your state is pretty simple.

Conclusion

I hope this step-by-step, evolution approach sheds some light on the usefulness of useContext and useReducer. They both have simple concept, yet once properly combined, they bring out power similar to that of Redux. They are certainly not a panacea, but they are good enough for many regular use cases.

--

--