 We want to talk about Redux at scale. Many of us are using Redux in production right now, in our applications. Many of us love it. Or should I say, many of us used to love it until they advent of GraphQL or, say, Hooks API in React. But there's one thing that, whether we love it or not, it is going to stick around for some time. So we need to get some things straight, some things better than what they right now are. So there are three major points that we want to talk about today. First is we want to establish some drawbacks that there are in the current architecture of Redux, how we use it currently. Secondly, is there a better solution to that? Is there a better architecture we can use to solve those problems? And last but not the least, we want to take a look at a bigger picture. See, whatever we learn today, whatever inputs we have today, can we apply these things to some other things as well? Is it just limited to Redux or can we take some inputs from this example and apply it to everything we do in our daily lives? So yeah, what do we want to achieve today? Well, I hope this is visible to everyone. This is just a json of some state that we want to achieve through Redux. And we'll be writing reducers to achieve this state in two different ways. The first one, of course, is how we do it right now. So let's look at the traditional way, right? So this is how a final reducer right now would look like. We'll have a combined reducer combining all the individual reducers. And what some individual reducer will look like? It will be something like this. So this will be the main logic inside a reducer that was handling some array-like state, right? It'll compare the action that was dispatched to the action type on which we wanted this reducer to work, update the state, manipulate the state in whatever way and what it took, and return us the updated state. And if the action type didn't match the required action, it will just return our state as so at its, as it is. Not that bad, right? But what if this reducer, we wanted to write another reducer that was not handling some array-like state, but it was handling some object-like state. What you'll do is you'll just copy this file, paste it into some other file, just change the action type, just change the spread. You'll spread an object instead of an array, and you have a whole new reducer working for you, right? There's a problem here. There's something fishy going on already. You are repeating yourself. You are repeating a whole file just to create another object, just to create another reducer that works similarly, almost similarly to what your earlier reducer looked like. So that's one issue. But that's still not that bad, right? Let's talk about what you do when you have some state where you want to load some data. You want to show some async states, right? Let's take an example at our code, look at our code. So this is some reducer. This is some reducer. Let's take some state, it takes some action, manipulates our state, and lose all the stuff for us. What it is doing is it is comparing the action type that is dispatched against some standard actions we have set out for ourselves, like set loading, set success, set failure over here, and reset, right? It is manipulating the loading keys, is loading each this and that and everything. But you can see this file already is almost 67 lines, right? And you'll have, say, hundreds of loading states in your application. You can't go ahead and write 6,700 lines for your code. You can, of course, we are developers, that is what we are meant to do, but we are lazy, we don't want to write that, right? So that's another problem. And just to take a brief look, there are three points we want to focus on, that we are writing too much code, that is repetitive too. We have seen that already. Second thing was, there's no really meaningful way to enforce standards across your organizations. One of your developers can say that here, I don't like this term is loading. I want to call it is loading. Third developer will come and say here, I don't like the term load. Whatever you say, I don't want to use this term load. I'll use fetch. So you do not have a meaningful way to enforce any standards across organizations of doing things and all that. So what do we do? Well, we'll come to that. But the last thing we want to focus is on, it becomes standard to write reducers for on a lack of a basis, right? For every use case, that is just a little bit different from what you have already solved, you go ahead and write a whole new reducer, that is not good. That is not good. It is wrong. These are the basics of programming, right? So, and it's not good at our part to just point out the problems, right? We have to provide a solution. The solution we provide looks something like this. On the left-hand side is again the same JSON that we wanted to achieve. And on the right-hand side is all the code you need to write to achieve that structure. These are all the reducers combined into one. So what we are basically doing here is we have created a utility called create reducer. We are calling this utility with an array of configuration and each individual configuration over here is capable enough to create a reducer for us. Now for those who are sitting at the back can't see this code, we'll just take a closer look at this. So say this was our state we wanted to maintain and a reducer that will manipulate and that will handle updates for us in an array-like state. All the code you'll need to write is this. Call create reducer with a reducer configuration that takes the action type on which you want to work. And along with that, under the options, you can provide it the initial state that I want this to handle as an array. I want this reducer to handle an object-like state and things like that. All you would need to change to create this reducer, handle an object-like configuration is go ahead, change this initial state to an empty object. You want it to be a Boolean just changes to false, changes to true, whatever you wanted your default state to look like. Cool, but what about the sync state? That was the real game changer for us. We had to write so much of code and it was complicated. Well, this is all of the state that you have to manage. This is exactly the same code that we were writing before. Just we have added one more key, a sync, set to true, right? We have told our create reducer method that we want to create a reducer once again. It's just that we wanted to handle a sync state for us and that is it. I know it doesn't make a lot of sense right now, but we'll just dive into the code a bit, just make ourselves a bit more familiar with this thing. So yeah, this guy is excited. So yeah, we have got some examples. So I think we'll launch this sync example. Is this visible to everyone? First of all, yeah, cool. So what we have done is we have called our create reducer with this configuration. We want to work on this action type and we have provided an array-like initial state and we have established this action type and reset type, some standard actions we have created using create action type utility. Initially, we have set a state to be undefined as you can see in the under the watch on the left-hand side. It is undefined initially. And to simulate this patching of actions, we are just gonna call our reducer with the current state and some action that we want to dispatch over here. The first time you dispatch some action, our state is going to change, but how? How our state changes? Since this action was some random action, it's not the action, same action type we want our reducer to work on, but since the state was undefined, it has returned us the initial state. So a state on the left-hand side, we can see it looks like an empty array, which has got nothing in it. Now say you want to add some data to your state. All you'll do is, you will dispatch another action over here in this line, provided the current state, provided it the action type, and the new payload you want to add to your state, right? That's how normal flow in Redux works. The update is like that, our state now on the left-hand side, we can see has changed. It now contains three keys, A, B, and C, what we had provided in the payload during dispatching our action. No issues with that. Now let's say we wanted to go further ahead, we want to add some more data to the very same state that we have already achieved. We'll dispatch another action yet again, and the update will be something like this. Our state now has got these three new keys also, along with A, B, C, and C, A, B, and C that we had already had over here. But let's say this time you have some new data and you want to replace your previous state all together, right? You just want to replace it all together with the new payload you have provided. You have provided. So the reducer we have created also handles this meta key inside our action. If you say that your merge has to be false, I do not want to merge the new payload I'm providing you into the existing state. It will go ahead and merge these things, sorry, not merge these things since we have provided merge as false. And this time we can see we have replaced the older state with the new payload. And similarly, reset works. It will just reset our state to whatever it used to be, when we didn't had any data in it. So that's this example, but how does it work, right? That is more important. How does our create reducer work in this case? You can see we have provided a single configuration, not an array, and we have set async to false. Now, by default, it is false. So what this create reducer will do, it will just go here, it will check if it is an array, the configuration we have provided, no. It will go to create base reducer. Inside base reducer, is our configuration a string? No, it's not a string, it's an object. So we'll just create reducer with object like config. And over here, by default, a sync is false. So we'll create a sync reducer. It is cool, until here, right? Yeah, so inside create sync reducer, all we are doing is, using the action type we have provided in our configuration, we create these two static standard actions. We call them action type and reset action, right? And we return a base sync reducer. This base sync reducer is nothing, but just a reducer function. It takes some state, it takes an action that was dispatched. And it has got this whole switch case, where you compare the dispatched action to the action you wanted to work on, and the reset action. It has got all this logic baked in inside it, right? It is updating the state for you automatically, merge data handles everything, array, object, whatever it was like, and things like that. So this is how this basic example was working. But let's say, now we want to handle our async state. We want to have those extra keys is loading and loaded and everything like that to work for us. Over here, we have again created our reducer. This is reducer with async state, but the only difference this time is that we have added this async key over here, right? We have told our create reducer function that this time I want this reducer to handle async state for me. I have given it async set to true. And then again, we have used create action types and created these four standard actions for us. Set loading, set fail, set error, and reset. These are four states we usually have in any async loading state, right? We have initialized our state as undefined, as we can see on the left-hand side, state is undefined. The first time this reducer is called with any kind of state for any action, it initializes the state for us. And this time you can see, we had provided initial state as data. This was our initial state. We had called that initial state needs to contain a key data that is an array, but we have whole lot of other keys present over here, we have got error, set to null, we have got is loading, is it? Yeah, is loading set to false? See, I did it myself. I was showing you loading an example, now this is is loading, right? So we have got is loading as false, loaded as false, has error as false, and all these things. These are already baked in into the reducer we have created, it is handling these state updates for us. So now let's say our user went ahead, started loading some data. What we'll do is we dispatch an action, provided the current state, and set loading action. So what this will do is it just went ahead, everything in our state remains the same, but our is loading key has not been set to true. So yeah, in UI, in front end, we can now show our users that we are fetching your data, it'll come in some time, just sit back, relax, and enjoy the show, right? So once our API has returned us our data, we want to set this data into a reduced state, right? So what we'll do this time is we'll dispatch a success action, and inside the payload we'll give it some data, that this is the data I want to be added into my state, along with some metadata. What this will do is, this time our state has got data as an array, containing three keys, A, B, and C, and the rest of the keys are already handled. You can see that is loading is false this time because we have set in success action that yes, we were able to successfully load our data, our loaded is set to true, has error and everything is false, error in this case is null. And we can also see the yo key, yo. The metadata we have provided in our action has been added to a state too. Now let's say this was, this reducer was attached to some filters in your UI, right? User went ahead, he changed the filter, what you want to do? What you want to do is you want to fetch new data, corresponding to the new filters, replace the old data that you have fetched all together with the new data. What you do is you again go ahead, start loading, so yeah, is loading is true this time, loaded is false this time. And when you receive your data this time, you tell your action dispatcher that I do not want to merge this new data with what I previously had. So this also handles this meta key merged false. You go ahead and yeah, all this data has been replaced with the new payload we provided. But then again, users are notorious, right? We don't like them. They make our lives really frustrating. He again went ahead and changed the filters yet again. So what can we do about it? Nothing. We just again had to go and fetch the new details. We started set loading, but this time our API failed. You just need to get on a call with your backend developer now. This time your API failed, what you do want to do is you want to set a error state in your Redux. This time, we dispatch a set failure action along with the error inside the payload, right? How that updates our state is, this time you can see is loading is false because yes, we are not loading data anymore, but loaded is not true. Has error is true this time because we face some error fetching our data. An error key has got whatever error we had provided in our payload and the rest of the state remains the same. And again, once you have got a corrected state, you go ahead and reset your state and that does it. Okay, that's fine. But how does it work? This time all that is different inside create reducer is that this time this async was provided as true, right? So instead of going ahead and creating sync reducer, we'll go ahead and create a async reducer. This async reducer again takes the action type we provided, creates four standard actions he wants to work on and provides us the default async reducer. This default async reducer does nothing, but maintains these states, these additional states that you would want to have in any async loading data and manipulates these states based on whatever action was dispatched. If the action was set loading, it will just set is loading to true rest of the fields will be set to whatever they need to be, update the state as and whatever update is required and return as the updated state. So yeah, that's how create async reducer is working. The only thing that's left to discuss about create reducer is what happens in the case of when you're providing it an array-like configuration, not an object-like configuration, right? That was the initial example we showed you. So what we do in that cases, we go ahead and call create reducer with multiple configurations. It maps over individual reducer config. You have an array of individual reducers and you just flat combine these reducers. This flat combine is nothing, but just a tidbit customized version of combined reducers that is provided by reducers itself, right? So this is what we people are doing. This was our solution, but how does it help us? That is important to know too. The first problem we faced was too much code. As you can see, initially the initial example we showed you, including all the reducers, all the typical reducer files, our initial source code was 130 lines. Now our source code is just 40 lines. And please note it, this was just one reducer state in your application. You have several of these in your application. As in when the number of states increases, the number of reducers you write increase, this difference is going to get bigger and bigger linearly. So yeah, our broken heart is not broken anymore. We have got it replaced with the brand new shiny one. The next issue we want to discuss was repetitive code. As you can see, these are two different reducers handling two different kind of data. But all the code that is different is this key and the action type. These needs to be different, right? These are two different reducers. They have to be attached to two different keys in your state and they need to work on two different actions. So all that you are writing once again in another reducer is the code that you exactly need to be different from the previous reducer. You're not writing any repetitive code. That's one move, shiny heart for us. The next thing was key standardization of code. As we have already seen, the create reducer method already puts everything baked inside the reducer, right? All the keys, all the manipulation of these keys are handled by create reducer itself. Your individual developer cannot go ahead and do things his way, right? He has to follow whatever is being followed within your team or within your organization. So yeah, that's better too. And of course, we are not writing any code, right? We are not writing specialized code for every use case. We are writing just configurations. So no need to discuss this. And I think this is it. This is all we wanted to discuss. Just one thing that's left to discuss is the bigger picture we wanted to talk about. So does this example we talked about, this whole creating a small utility over redux? Does it work for redux only? No, it doesn't, right? You can go ahead, take any customizable utility, redux in this case, write a wrapper over and above it and expose it as a, you can say, configurable utility. In this particular example, redux is such a huge utility, is such a huge library. It is used at hundreds of companies in tens of thousands of different use cases. Redux as a library maintainer cannot, just cannot go ahead and force you to follow this particular pattern, right? It cannot. But you as a developer within a team know that this is my scope of area, this is how I'm going to use redux, right? This is how I wanted to work. You can go ahead, write a wrapper above these customizable utils and expose a configurable utility for your developers. And believe me, it creates a better developer experience for every developer that is joining your team, a new developer, or a developer that you have shifted from one team to other, see? The code that he is going to read in other teams' code base is not going to be any way different from what he was already writing in his earlier team. So that is one more better thing. You have got increased speed of development, right? The development speed actually enhances using this kind of configurable utilities. And the very most important thing, most important thing about this utility is that the scope, you say, the code that you are writing that can get bugs, that can introduce bugs in your application is reduced. It is reduced by bounce and leaps. If you are writing 100 reducers, you have got 100 files you need to write tests for, right? There are 100 files you need to test. But if you have created this one utility, you need to write your tests once, and each and every one of those 100 reducers, you have got them covered. So yeah, a whole lot less of those late night calls in the midnight at 1 a.m. to a.m. and Eureka moments will be much lesser. So yeah, that should be it from my end. Yeah, thank you. Thank you, folks. Thank you for coming. This is... Yeah.