Learn how TypeScript can benefit your Redux setup, by getting type-safe actions and reducers with the help of the amazing library Deox, and how all of this perfectly integrates with redux-observable (RxJS)
Building one of our web apps with pure modern React at Move Digital AG has gotten us far. The codebase was easy to follow and any logic was straight forward and easy to understand. We always made huge progress in short amounts of time.
Until we hit a certain point, where our current project grew so much that the need for a full fletched state management solution became more and more obvious. So I decided to investigate and evaluate possible solutions, inspired by our current needs and the technologies I loved to work with in previous projects.
In this writeup, I'll assume you already are familiar with the Flux pattern. For our examples, I'm going to use the Redux implementation. So first off, I will offer some insight into how TypeScript can benefit your Redux setup and then follow up on how to overcome some of the typical issues with the help of the amazing library Deox. At the end of this article, I will also show you how this perfectly integrates with redux-observable.
Adding TypeScript to your codebase comes with many benefits and also a few drawbacks, but I don't want to go deep into why we would want to use TypeScript in the first place, as there are many existing articles on this topic. Instead, I want to show you, how to make it work nicely together with Redux. Starting off with the two technologies by themselves, you will probably write some code like this:
After creating your action type constants and defining your types, you may implement a reducer function:
Usually, you would implement this using a switch case on the action.type. Please note how the action including the payload will lose its typing information by doing so.
As you can see, there are a few cases where we won't get any type-safety. Sure, we could type-cast here and there, but I think that would somewhat defeat the purpose of it all.
With the default way of setting up a Flux pattern (as described above), TypeScript can for example not figure out automatically what state is to be returned in the reducer function. Or what that payload of ADD_TODO is going to look like.
Because this hurts the developer experience and the benefits of TypeScript quite a bit, some developers came up with a neat way of coupling some of those things that belong together. Enabling us to easily build a type-safe Redux architecture.
Deox was created with its main goals of enabling type-safe flux architectures and minimizing boilerplate code. It's straight forward to use and easy to read. But most importantly, you will finally reap the full benefits of using TypeScript.
Using createActionCreator you can define your actions. Actions now contain the type as well as the resolver function, which can be used to do some minor data preprocessing before it gets fed to the reducer. String constants for the type (like ADD_TODO) are not necessary anymore, as you will use the created action object itself.
To define our reducers we use Deox' createReducer function. It infers the type of our first argument, the initialState, which is then used to type the return values of our handlers as well as the state argument that goes into our handler. The handleAction function again makes use of our typed action object, wherefrom it can infer what the payload must look like.
And hazaaa 🎉 you won't ever again write a reducer only to find out at runtime that a wrongly assumed payload is has corrupted your state!
Going forward you will want to dispatch your actions within your components. In case of using React, this should be done using the helper arguments of connect, to map state/dispatch to props. And now imagine trying to call addTodo with a string as its argument, instead of a Todo object. Thanks to our typed actions, the TypeScript compiler will give us immediate feedback regarding the wrong type of your passed string argument.
Needless to say, any state you would consume will also be correctly typed.
What's cool is that all of this also translates really well to asynchronous middleware handling. In this example we're using redux-observable, the RxJS based alternative to redux-saga and redux-thunk.
Setting up a simple Epic that fetches our todos asynchronously could look like this:
As you can see, we can again benefit a lot from our typed actions and payloads. Imagine how amazingly simple it would be to refactor some major state with all these types in place. And the autocompletes and return type checks within all of your RxJS operators. Also, thanks to RxJS' powerful integrated operators, you can declaratively implement cancellation of requests, error handling and retry strategies, delays, buffering and much more.
I hope I could give you a valuable first impression of how these technologies work together.