Making the react + redux + redux-saga development more fun and productive and less repetitive and verbose.
I have recently been working on and delivering some projects using pure react and react + redux, and it seemed like something was missing that would make working with them more reliable. Early last year, I started learning new front-end architectures using react to a POC for one of my customers. I came across the combination react + redux + redux-saga, which felt pretty healthy to me as the right solution for lots of problems I had faced in recent projects. Since then, I have wanted to try the combination in a real-world project to see if it scales or not. The opportunity came early this year when I needed to create a new mobile UI for one of my personal SAS solutions, so I decided to give it a shot. After developing the first set of features, I found myself doing a lot of repetitive work that delivered few new features or improvements.
Therefore, I decided to stop for a couple of weeks and see how I could make the process better, more fun, and more productive since I have little time to work on my projects (most of my income comes from my consulting job at [Radicalimaging], http://radicalimaging.com). Moreover, I want to make the most of the little time I put into them. For the most of this post, you will need a good notion of the technologies I used, so I suggest if you are not familiar with the React + Redux + Redux-saga workflow, you may want first to read this blog post, which gives a complete yet straightforward example. However, here, I show a before-and-after code to help you understand the improvements I am proposing.
Why do I think the workflow is repetitive?
To illustrate the repetition, I am using a feature I have just delivered as an example of the improvements and how the process looked before all the changes.
The feature is: “As a user, I want to search for a customer by name, an And if no customer exists, we should give the user the option to create the customer using the already typed name. Of course, as good developers, we also want to show loading properly while the request is not finished, show the error to the user when and if it happens”. (Note: I omitted all the UI code to simplify the thought process, and they would be the same for the first and the last implementation.)
The feature is pretty standard, so let’s start thinking about the state
we need for this use case.
This model should not change over all our refactoring here. Now let’s see how the events, reducers, and sagas for that would look (here is the old and repetitive version).
Don’t you think this does not sound that repetitive? But all this is for a simple feature of searching for customers for a field while scheduling an appointment. Imagine that each interaction with API requires the same amount of code, and it also requires a considerable amount of tests to cover it as well, and we need to maintain it in the long run.
How does the final code look?
Here is how my code to support the same features looks at the end of this refactoring.
Next, I will show how I got to that point, but first, let me make a disclaimer. My goal with this post is not to create any new library (we have too many already), but to show how you can make adjustments to your app that save 80% of your code, and let your only worry be about the other 20% that is different and needs special attention.
Making your own reduxsauce
The action types
First, let’s start on how to generate the events constants. In this case, we need to generate 3 CUSTOMER_SEARCH_ACTION, CUSTOMER_SEARCH_SUCCESS, CUSTOMER_SEARCH_ERROR,
which is the case for most of the actions my app needs. What is common among them is ${prefix}_${suffix}
, where prefix is the operation name and suffix changes for each operation kind. How can we auto-generate this?
Now that we can call this from our dock and re-export it, we no longer need to create the 3 constants for each new interaction.
The actions function
Let’s see the action functions here in the example I have manually written:
Analyzing this function by the name, customerSearch
, is a camel-case version of the operation I want to create. The return value is the payload received by the parameter, and present in the type is the ${mainActionName}_ACTION
or actionName
, as we have defined in the last sections ^. Now let’s change our serverInteraction
function to match that criteria: first, we need a function to transform snack-case into camel-case . Next, let’s find an npm for that let’s create a simple function for that.
Next, change the function to create an action on runtime and return the name, as shown in the above example.
Now from the ducks part we are already capable of doing something like
This eliminates all the boilerplate needed to create all that stuff.
Reducers
We need to generate 3 reducers, one for the action event customerSearchReducer,
one for the
success request customerSearchSuccessReducer
and one for the error case
customerSearchErrorReducer.
Additionally, they bring the complexity of dealing with the loading behavior. As you may have noticed already, the customerSearchReducer
sets the loading to true,
and both customerSearchErrorReducer
and customerSearchSuccessReducer
set it back to true (yep; it’s that simple). Let’s start with the simplest case.
The customerSearchReducer
Transformation
The customerSearchSuccessReducer
Transformation
The customerSearchErrorReducer
Transformation
Saga (This one is a little bit more fun)
The saga code I had to automate had 3 main parts: 1) call the backend; 2) report success and send the request data to the reducer store; 3) notify if some errors occur. Now let’s analyze the old code and generalize it.
Transformation
Final code (Without code comments):
Final thoughts
Those 60 lines of code reduced the amount of code produced to add a backend call of my application in 74.41% (or from 43 lines of code minimum to 11 lines minimum), and I got loading, done and error state control by design. So I no longer need to count on my motivation to implement each one of these things for each new API call I want to implement in the future.
I am happy with the result of this generalization in my application. It results in the app being much more responsive in the UI and using less code. Once again, my goal with this post is not to make a library (the React community has enough already) but to show my idea to generalize a part of my app that was driving me crazy and eating all of my motivation to deliver new features to my end users. I hope it can help you write your libs/frameworks to your app and make the process of adding/maintaining your projects more fun and less manual.
Finally, there are several ways to solve the problems I have exposed here, including the structuring of a React application. Still, I often find myself involved in projects where more than one pattern for doing things is preset. The implementation of opinionated and general solutions like the one presented here can save your projects a lot of problems and help you painlessly scale your app.