If you’ve been using some form of Redux, you are familiar with the basic flow of data through the Redux loop. Central to this flow are Actions, the messages that trigger code in our Reducer or Effect/Epic/Saga (depending on what flavor of Redux you are using).
The model allows us to disconnect our code so that it only cares that an action was triggered in some way. That is, “when this action occurs, I will run this code.”
Because of this, we can create an action that triggers multiple code blocks to run. Our only concern is that the code that gets triggered can’t depend on each other.
In fact, much of the official literature encourages this practice.
And, this is where all our troubles begin.
You see, the official literature around actions also suggest a naming convention that is tied to what we want to happen, rather than what just happened or something otherwise more generic.
For example, take the simple act of loading a list of employees from the server. To do this, we would typically create two actions. The first would be
LoadEmployees and the second would be
LoadEmployeeResults. Both of these actions represent what we want to do, not what just happened.
Now, let’s say we also want to display some sort of wait state and that we want to control it using Redux. For that we would create a Reducer in our store. Let’s call it
Wait. And for the purposes of this post, we will assume that
Wait tracks wait state by incrementing and decrementing a counter.
But, we don’t need to create new Actions for our Wait state because we can re-use the actions we’ve already created. When we fire
LoadEmployees we can increment
Wait and when we fire
LoadEmployeesResult we can decrement the load.
Do you see the problem here? We now have a reducer responding to an event that is not obviously tied to an action that has anything to do with the code that is getting executed.
Wait is not a
LoadEmployees thing although, you might reasonably say it is a
And so how might we think about restructuring our code so that this makes more sense.
What if instead of naming actions what we wanted to do, we named them something more along the lines of what just happened. That would make our
NeedEmployees and our
Even these names don’t get at the fact that Needing Employees doesn’t mean we need to increment the wait state.
One way we can think about this problem is to look at other messaging systems, like the windows messaging system. It’s been a while since I’ve used the raw messages in Windows but I seem to recall a
WM_BUTTON_CLICK message that would get passed to my application when a button was clicked. One message for every button in my system that had, as part of the payload, what button was clicked so my code could listen for that message for that button and do something because of it.
We could do something similar with the code above. Instead of tying the actions to particular code we want to run, our actions could be more generic.
What if we had two generic messages,
LoadResult. The payload for these messages could then have a
LoadType property that defined WHAT we were loading so that we could Load Employees, Addresses, and Phone Numbers all using the same two actions and we could also use
LoadResult to increment and decrement our wait state.
This would work except we still have a problem.
Most of the tools we currently have in place to reduce the boiler plate code we need to write are based on a one-to-one relationship between actions and the code that gets run. This isn’t to say that we couldn’t adapt them but doing so would be more trouble than it is worth because we would be working against the intended design.
And so, I would suggest to you that the advice the current literature gives us about using Actions to trigger multiple blocks of code is wrong. Since Actions are primarily used as loosely coupled method calls, using them in unrelated places violates the Single Responsibility Principle and makes the code hard to follow.
I’ve seen one code base where the code that gets triggered has absolutely nothing to do with the Action that triggers it. Not something in the simple case of LoadEmployee triggering Wait, but in more obscure relationship like
LoadSteakDinner. The result is that the code becomes extremely difficult to follow. This is one of the biggest causes of bugs in this system.
For our discussion about the Wait state, I would suggest that we create a separate set of Actions for our Wait state, possibly WaitStart() and WaitEnd() that get fired when we fire EmployeeLoad() and EmployeeLoadResult(). You could wrap these calls in a function or method to ensure they always get called together, but the actions themselves need to be unique to make the code easier to follow.
Currently, this is my recommended implementation. While not ideal, it gets the job done and makes maintaining the code simple and straight forward even if I do have to duplicate more Actions that I would like to.