Redux side effects and you
Should you care about alternatives to the venerable redux-thunk?
It seems every couple of days now, someone comes up with a solution for side effects in Redux to replace redux-thunk. Thunk is the go-to middleware that allows us to return functions from action creators to dispatch action objects synchronously or asynchronously. If you’ve never used it or don’t know what I’m talking about, the official documentation describes it pretty well.
But why does it matter, if at all? Are they worth considering?
Edit 3/27/2016: Highly related to this post, see the following discussion on the Redux github repo about side effects handling
If you’re happy with redux-thunk, stop there
It’s pretty much that simple.
If you’ve never heard of those alternatives, or if you’re wondering why you should care, you can stop worrying about it. Thunks will work just fine. They are simple, easy to learn and flexible. They will allow you to build the largest, most complicated application you can think of.
Only look further if you feel you’ve mastered Redux and feel it’s not enough. Whatever you do, do NOT try to make your own middleware to handle asynchronous actions and side effects until you’ve already looked at what’s out there.
The search for the one true model
If thunks are so great, why are there so many smart people trying to come up with alternatives?
It generally falls in two big categories:
- They are harder to test
- Mixing actions and side effects in asynchronous action creators drastically increases complexity.
That’s why every couple of days, someone tries to find a more elegant way to solve these problems, and we end up with countless side effect “frameworks”. This is very much like how, until Redux became popular, a new Flux framework came out every other week. I don’t think it’s a bad thing. A better de facto model to handle the hardest piece of Redux development would be great.
Testing is a solved problem
First, I’d like to dispel the notion that we need an alternative to thunks because of testing.
Yes, testing with redux-saga is beautiful, thanks to its declarative side effects, mocking dependencies work great too, and is often simpler. Look again at the Redux testing recipes under “Async action creator”.
Declarative side effects are a way to describe an effect, such a fetch call, as an object instead of calling the function directly. In Redux’s case, a middleware would then handle calling fetch for you. When unit testing, instead of mocking fetch, one can simply inspect the object to make sure it describes the call directly. The actual fetch function is never called.
It really isn’t that complicated, and many would argue it’s simpler than learning a whole new tool during development. You can keep using the simple thunk the way you always did.
Many will argue that modules aren’t a substitute for dependency injection and breaks the inversion of control pattern. I disagree. The syntax makes it look that way, but the semantic is very much “I declare that I need this dependency”, and it is up to the module system to provide it. The fact they look like factory method calls is just syntax sugar. Adding another DI framework such as Angular’s is redundant.
We are left with weaker or more complicated tooling that is often environment specific.
- inject-loader is Webpack specific and isn’t built with ES6 modules in mind.
- babel-plugin-rewire works at compile time, is babel specific and has a complicated API.
- Mockery is node specific
I really think the community should not let efforts to make writing testable code easier stop it from developing better testing tools. They’re complementary. We need both.
Don’t ignore what has been done before
This is a bit of a pet peeve of mine. Every couple of days, someone who has been learning React/Redux for a few weeks comes up with this revolutionary idea to reduce boilerplate or. in this case, to handle side effects. The vast majority are just rehash of the 60 other attempts that were made before.
Worse, they usually haven’t even tried the existing solutions, which often would have solved their problem already. Recently I’ve seen one where the author hadn’t fully grasped redux-thunk before starting to make their own. Thunk is 8 lines of code including the blank line and brackets. Sagas, Loop and the others may not be perfect, but they are very solid attempts at simplifying side effects.
More importantly, across all the attempts we’re starting to see clear themes coming up, and any new tool should explain how they’re working with those themes, or why they did not. Here’s some of the themes I look for in every new tool I look at.
Declarative side effects
To avoid callback hell, simplify the control flow, and make testing easier (oh no, I fell for it too!), there’s a lot of gain to be made in having declarative side effects be a first class citizen. Almost all of the attempts at side effects models go that route.
Synchronous actions only
Sagas, Loop and actors all get rid of asynchronous actions altogether. Action creators go back to the default (returning an action object), and some kind of external listener or even the reducer takes care of side effects. If the side effect isn’t declarative, that just moves the problem elsewhere, though.
Changing the Redux pattern
Redux is a design pattern, and it’s great, but many people like to experiment with tweaking the pattern. In Redux-loop for example, modifying state is considered just one of many side effects handled by the reducer, which is a significant change. Those aren’t necessarily bad things, but authors should be clear about what they’re changing and what are the trade offs.
Comparison with Elm or functional programming
Redux takes a lot from Elm and functional programming in general, and for good reasons. FP patterns are great at keeping function simple, pure, testable and keeping state or side effects isolated.
When bringing forward a new way to handle promises, http requests or other side effects, it’s likely there are parallels in Elm, Haskell or Elixir communities, to name a few. I like to see these brought up in the conversation. “We’re doing things this way because it is a problem that was solved in FP language XYZ and it worked well”. Let’s not reinvent the wheel.
The next leap
Like Redux did for Flux, I think we’re getting close to the point where one of those middlewares or tool will raise above the rest as THE way to go when developing React applications. It’s possible one of the existing solution will be it, or that a new one will come out of nowhere.
To get there though, we have to attack the right problems, and build on top of what we already have instead of starting over every time as if no one else tried before.