Rebuilding Redux with Hooks and Context
A simple global state management package based on React constructs
There’s been a lot of hype recently about React Hooks and what they allow developers to achieve. Indeed, in the near future, we will be able to rely on a single React pattern to build pretty much anything we want. As of today, React consist of a lot of patterns, if not too many for some people: Stateful Classes, Functional components, Higher Order Components and render callbacks to mention just a few.
The React core team expressed several months ago their desire to slowly phase out React Classes. Hooks, along with Suspense, which I talked about in a previous post, are the main building blocks of this plan.
In this post, however, rather than focusing on how hooks impact React components themselves, I want to go a bit further and showcase how they can be used, in conjunction with the already existing Context API, to build a very basic implementation of Redux. The example I will provide covers the basics functionality of Redux for global state management.
For this example, we will consider a simple application. It will display some message that can be fetched through a Redux action
FETCH_DATA which can be triggered by clicking on a button.
Provider and reducers
Let’s consider the following reducers:
As we can see, this is the kind of reducers we’re used to seeing in any Redux based application. The objective is to have the same reducers working for our implementation of Redux.
First step: Defining our
This will be the core of our reimplementation of Redux. The Redux Provider works quite like a basic React Context Provider, so we can base our work on the Context API. Our store Provider will wrap our app and let it access our store object at any level. Here’s how it looks like:
createStoreWe can see above the mention of the
createStore function. If you’re familiar with Redux this should ring a bell. This function takes our reducer, and the initial state object of our app returns an object with 2 essential items that are injected into the app through our Provider:
- dispatch: the function that lets us dispatch Redux action
- state: the object containing the global state of our app.
To reimplement this function in our example, let’s use the new React hooks. React has a very handy pre-built hook called
useReducer which actually returns these 2 items stated above:
We now have all the elements for our implementation of Redux to work! Below you will see the code of our basic app that is using the examples above to dispatch actions and get some data from our store.
However, we can see that although the constructs we came up with are quite similar to the ones of Redux, the way it’s used within an app is not quite the same. This is why I wanted to push the example a bit further and reimplement the
connect Higher Order Component.
Rebuilding the Connect HoC
For this part, we want to achieve the following:
Given the code above, our connect HoC must take 2 optional arguments: a
mapStateToProps function and a
mapDispatchToProps function. It will then inject the following items as props for the wrapped component:
- the objects returned by
With this implementation of
connect, we now have a more familiar way to access the state from our components.
Going even further by adding middleware support
One other thing that would be nice to have in our reimplementation of Redux would be some support for middlewares. In this part will try to emulate how middlewares work in Redux, and try to end up having a similar implementation.
How do middlewares currently work?
In a nutshell, middlewares are enhancements to the dispatch function.
Middlewares take a store object as an argument, which contains a
getState function and a
dispatch function, and are then composed to finally give us an enhanced dispatch. By looking in the Redux codebase we can see that this enhanced dispatch function is a curried function where the middlewares are “composed” and then applied to our dispatch.
Compose here means that instead of having to write for example
f1(f2(f3(f4))) we can simply write
Note: This short summary and the code implementation below are based on my own research and on this article.
We can now add a basic middleware to our
createStore function. Here’s one that logs to the console any action that is dispatched:
Thanks to the Context API and the recently announced Hooks, we saw that it is now easy to rebuild Redux. Is it usable? Yes, as we saw in this post, we covered the main components of Redux (Store, connect, middlewares, etc) and use them in a small app. Can this replace
react-redux? Probably not. Redux still has a lot more than what we covered in this article, like the Redux Devtools or the entire ecosystem of libraries that can enhance your app on top of Redux. While writing this post I’ve personally tried to add the
redux-logger middleware to our example, it “worked” but I couldn’t make it print the correct “next state”(maybe because the
useReducer hook is async since it’s based on
but as you can see in this tweet, maybe I was just a bit too ambitious.
Want to continue working on this project or just hack on top of it? You can clone the repository containing the code featured in this article along with a basic application here.
If you liked this article don’t forget to hit the “clap” button and if you have any other questions I’m always reachable on Twitter, or on my website. You can also subscribe to my Medium publication to not miss my next post.
What to read next?
If you want to read more about React or frontend development, you can check the following articles:
Asynchronous rendering with React
How the new React Suspense API might reshape the way we build components
Using Flow generics to type generic React components
How Flow generics help typing complex multi-purpose components
React sub-components Part 2: Using the new Context API
Further simplifying the sub-component pattern using contexts to make flexible, easily testable and reusable React…
Have a wonderful day.