Hello folks, today I’m here to show you guys a better way to structure your reducers, without the classic switch-case style on which most developers I've seen until today are so tied to.
The reason why I'm writing this article on the first place is mostly because the past few weeks I've been mentoring a junior developer and I've realized that when new programmers start using redux, they can’t see beyond the obvious structure shown on the redux docs examples using switch-cases everywhere.
When I showed my structure to this junior developer (let’s call it bob), he simply took a very long time to understand how things worked, in his head, and probably for most beginners, the idea of a reducer always tied to a switch-case, what is definitely not true, not even close that.
So the first thing we are going to do is to define exactly what is a reducer:
A reducer is any function that receives a state, and returns a new one, based on some information.
Based on this information, we can explain exactly the switch-case examples without care about the switch-case part of it.
This is a very common example of a reducer, and we can confirm that it matches the reducer definition, it receives the current state as parameters, and based on some information (the action type, in this case) it returns the new state. What I want to clarify here is that the switch-case is just there for convenience, it’s not mandatory, neither the best way to define our reducer logic, we could re-write this example simply by using if and else:
That’s totally acceptable based on the reducer definition, it also takes the current state, and returns a new one based on some information.
Now that we’ve got what a reducer is, we can start creating the new structure for it, and before we move on to that, I wanna let it clear that I’m not saying that this is the best approach or that you should use it for any reason, I’m just demonstrating a different way to write your reducers that can bring you some benefits like:
- Reusable code
- Easier to understand
- Easy to extract logic to another file (we will see that later)
- Easier to maintain on large teams
- Focus on what matters (the logic), forget about the switch-case
Let’s start by creating our counterReducer using our new structure, and then we can start explaining the benefits above.
So, our counterReducer looks like this on the new structure
Now, what is this weird syntax? what is the handlers object? what is the reducerFactory function? what does it receive as parameters? those are some questions that may show up on your head when you see this code, let’s explain all of these questions, one by one, and then if you have some others, just let me know in the comments.
What is this weird syntax? and what is the handlers object? Great question! on the new structure, we replace switch-cases for objects, that’s the handlers object, every single property one this object, is a function that receives a state and an action as parameters.
What is the reducerFactory function? and what does it receive as parameters? Also great question haha, this function is responsible for creating our reducer based on some state, and an object of handlers, so, every time the reducer is called, it will check if there is a handler defined for this action, and if there is, the new state will be the result of this handler. It’s easier to understand using a graph:
On this graph we can understand exactly what is the reducerFactory’s purpose, it receives the action, check the existence of a handler for this action, and if there is any handler for this, it will update the state with this handler result, otherwise, the state won’t change.
Now let’s take a look at the reducerFactory code:
And that’s it, nothing else, basically we receive the initialState for our reducer, and a handlers object. When an action hit’s this method, we get the handler based on the action.type and if the handler is not undefined, we return its result passing the current state and the full action as parameters, otherwise we just return the current state, as no handler was defined for this action.
Now that we know how this structure works, let’s talk about it with some other examples and all the benefits of using this structure instead of switch-cases.
Reuseable Code and Easier to understand
Well, those are for sure the greatest benefits of this approach, because we are not tied one an imperative switch-case code, instead, we have the full power of objects on our hands, we can define handlers, and modify them as needed, when needed, for example, you probably caught yourself writing reducers that just receive some object, and send it to store, something like
There is a lot of cases where this happens, you just received the state on the action, and sent it to the store, there was no check, no side-effects, you just wanted to update the store with your parameters, and you had to write a lot of boilerplate code for that. Thinking about these cases we can create one changeState function that does exactly that, and because we are not tied to the switch-cases anymore, this can be a generator function that just receives one parameter, something like:
With this function we can get the exact same logic, but with a much more readable style, the end result will be:
This is much easier to read, and it didn’t took not even one second for you to understand what the actions.SET_USER does.
Easy to extract logic to another file
That’s a pretty easy task to do since all we need on the reducer file is the handlers definition, it’s logic could be anywhere, so we are able to do the following:
That way we have the full power of functions to abstract our handlers logic anywhere we need.
Easier to maintain on large teams
The more the project grows, the more conflicts the repository will have, that’s not my opinion, it’s just the hard truth, if two people work on the same file at the same time, it’s almost impossible that the end result won’t generate a new conflict on the git repository, that’s why we should avoid at all cost create large files of code, because the larger the file, the larger is the possibility of two developers have tasks’s that involve changing that file. With this structure we can move the handler's logic to their own file, avoiding conflicts on the end result, and if even with that there is any conflict, it will be much easier to solve.
Focus on what matters
Well, this wasn't even necessary to say but, with this structure, it’s easier to focus on your logic, on your own file, without worry about stupid things like switch-case typos.
One last thing, all the concepts presented here works on the redux reducers, and also on the react useReducer hook.
That’s all folks, I hope you have enjoyed reading so much as I've enjoyed writing, any questions or ideas, please let me know in the comments.