Redux Toolkit is no longer needed?

The problem of the huge amount of boilerplate code when using Redux is known to everyone, everyone solves it as best he can. And we used different crutches and bicycles on different projects, without losing hope of finding something standardized and convenient. A little over a year ago, we despaired of our search and seriously took up the solution to the problem. What came of it is described below.

Appearance story


Our company is developing projects for startups around the world. And for these crazy inspired guys, the most important (or one of those) is time. Previously entered the market - higher chances of survival. Therefore, we always try to reduce development time without sacrificing quality, and reduce the number of errors caused by the human factor. For the most part, we choose something standard and more or less stable. In this case, nothing ready could be found, so they rolled up their sleeves and began to see what could be cycled here.



As usual, we started with a description of the problem and got the following:

  • Each developer has his own subtle accent when writing code, it is difficult to achieve the implementation of the concept of “depersonalized code”;
  • A lot of code (boilerplate, copy-paste and all the ensuing problems), 100+ lines on CRUD;
  • A lot of time is spent writing basic things. You will forget one thing, the other: set loading, handle errors, etc .;
  • The structure of the store differs from project to project, and sometimes between different parts of the project.

A correctly asked question or a formulated problem is half the solution. After a little thought, we introduced the rules for the formation of the store, described its structure and decided to refactor and comb the code. Despite the fact that our code base was not incredibly large, we immediately stumbled over the amount of code that needs to be changed.

While the PMs were puzzling over where to get a couple more hours of the day, there were a couple of enthusiasts who generated and implemented a simple idea to the point of banality. Yes, ingenious ideas, after they come up, always seem simple. And the idea was this: simply generate separate pieces of code instead of manually rewriting it. I did not want to use snippets and get sheets at the exit, because any changes led to disaster and repeated refactoring, so quickly sawed off a function that took some parameters as input and built reducer, action and saga. So the first version of communications was born ( @ axmit / redux-communications) The name "communication" was born somehow by itself, because this library ties together the sagas and components. And so it went ...

Immediately after that, happiness came. Well, or almost immediately. Development time decreased by about 20% (as we calculated it - the topic of a separate article). First of all, due to a significant reduction in the number of crawling bugs. And this is not to mention the fact that developers are much less likely to make stupid mistakes by inattention.



Linus Torvalds once said: “Chatter is worthless. Show me the code. ”And I completely agree with him. I won’t quote or rewrite the dock or give the code sheets here.I will give only a small example. Links to a full description of the library and a sandbox with examples can also be found at the end of the article.

Consider a typical task - we need to create CRUD for some entity. Take for example the task. I consider useless to describe the standard version, as it will take up a lot of space, and everyone who came across the editor is likely to roughly imagine how it will look. And in order to get communication, you need to do the following things:

1. Describe the transport, without it anywhere

const basePath = `/api/task`;

const taskTransport = {
   add: task => axios.post(`basePath`, task).then(r => r.data),
   get: id => axios.get(`${basePath}/${id}`).then(r => r.data),
   update: task => axios.put(`${basePath}/${task.id}`, task).then(r => r.data),
   delete: id => axios.delete(`${basePath}/${id}`, task),
   getCollection: () => axios.get(basePath).then(r => r.data)
};

2. Describe the name namespace
const namespace = 'task';

3. Create a strategy for creating communication
const strategy = new CRUDStrategy({
   namespace,
   transport: taskTransport
});

4. Create communication
const taskCommunication = buildCommunication(strategy);

5. Connect reducers and sagas from communication, as usual
taskCommunication.reducers
taskCommunication.sagas

6. After that, it remains to connect the side to the component
taskCommunication.injector(MyComponent)

7. And start using
componentDidMount() {
   const { getTaskCollection } = this.props;
   getTaskCollection();
}

render() {
   const { taskCollection } = this.props;
   return  taskCollection.data.map(item => <span>{item.title}</span>)
}

In fact, both the transport and the component will need to be created in any case, and the full communication code is as follows:

import { taskTransport } from './task.transport';
import { CRUDStrategy, buildCommunication, StoreBranch } from '@axmit/redux-communications';
 
const namespace = 'task';
 
const strategy = new CRUDStrategy({
   namespace,
   transport: taskTransport
});
 
export const taskCommunication = buildCommunication(strategy);


This is all you need to do to have a fully functional CRUD. If you need to do something more complicated, you can expand CRUD communication or use BaseCommunication. In an extreme case, under the hood it's still the same good old-fashioned editors with all its features. Flexibility has not been affected. Transport is placed in a separate layer, and its implementation can be anything. We have graphQL on our projects and simple queries using axios, and we did not experience any difficulties in this regard. An attentive reader may have noticed that the library exports sagas, and this is one of its most significant limitations. If for some reason you cannot use sagas, this library, unfortunately, is not suitable for you.

Why now?


The decision to write an article came after reading this article . Poking this tool, I was surprised to find that communications are much simpler, more concise, give a more clear structure of the store and at the same time are not inferior in flexibility. After sitting and sorting out an hour with the sources from the example to redux-toolkit, I rewrote it for communication. I tried to make a minimum of changes to make it easier to track the difference, although, in my opinion, the structure of the example is very confused. I specifically left comments on the code to make it easier to compare how it was and how it became. Pay attention to the * .communication.ts files and slices that they replace.



The fact that the number of lines is much smaller and the code itself looks much more pleasant (subjective) is not so important, because in prod we have quite heavy communications. There is one more important difference. The code is declarative. We simply determine what and where we want to get and what to do with the data, and how to do it - we do not care at all.
Conclusion - redux-toolkit should be used for customization, for everything else there is MasterCa ... @ axmit / redux-communications .

To summarize


  • The code has become consistent across all projects and with all developers. Similar problems are resolved uniformly, and solutions are often delivered in separate packages and reused in the future. At the same time, the amount of code has decreased significantly.
  • The June received clear and understandable flow.
  • Seniors rejoice that they don’t have to write tons of boilerplate code and are thinking how else to improve communications.
  • Debugging was simplified, and the structure of the store became simple and understandable to every developer in the company.
  • The transition between projects or different parts of the system does not cause a headache.
  • PMs are also happy. The number of bugs has decreased - customers are satisfied. Writing has become easier - developers are happy. What else does PM need for happiness?
  • You can move to communications gradually or even use a hybrid approach (communications + slices).

Of course, the library also has drawbacks.

  • Without sample code and documentation, understanding it is quite difficult.
  • Changing the structure of the store for an arbitrary will not work, because It is on the structure of the store that all automation is based. But it is worth noting that in our work we never experienced any difficulties with this.
  • You can only work with sagas. Thunk did not suit us, and we always use sagas, so this is not a problem for us. But if for some reason sagas do not suit you, then you will not be able to use the library.

I hope, despite the existing shortcomings, our library will be useful to someone. If you have any suggestions for improvement or questions on their use, please write, I will be glad to discuss.
If there are any cooler analogues that we could not find - let me know, we will definitely study them!

A full description of the library can be found here , and poke online here .

The full code of the example rewritten for communication from the ReduxToolkit docks is here , but you can poke it here .

All Articles