A guide to understanding Redux Thunk, Saga, and Observables
Created by Dan Abramov, Redux provides a predictable approach to managing state that benefits from immutability, keeps business logic contained, acts as the single source of truth, and has a very small API. To use an analogy, if you equate Mario to React, Redux is what would tell the game how long Small Mario can remain in his Super form after making use of a Super Mushroom. The beauty of Redux lies in how easily it allows developers to scale a simple app to a large, complex one.
Redux is built on three main components, namely:
- Actions: Payloads of information that send data from your application to your store. They are the only source of information for the store. You send them to the store by dispatching actions using the redux-connect library.
- Reducers: Are responsible for modifying the store (i.e. state) according to the actions dispatched.
- Store: Stores the whole state of the app in an immutable object tree.
The typical Redux app would have a single store with a single root reducing function. As an app grows, all you have to do is to split the root reducer into smaller reducers independently operating on the different parts of the state tree. This structure allows Redux to be simple yet powerful, because it is possible to trace every mutation to the action that caused it. You can even record user sessions and reproduce them just by replaying every action.
Remember, Redux is not an application framework, and does not dictate how effects should be handled. For that, developers can adopt any preferred middleware, and redux-thunk is arguably the most primitive of them. Redux-thunk is noteworthy in that it allows you to dispatch actions asynchronously. Written by Dan Abramov himself as part of Redux before being split out into a separate package, redux-thunk’s original implementation is tiny enough to quote in its entirety:
In simple terms, redux-thunk is a functional programming technique used to delay computation. Instead of executing a function right away, redux-thunk can be optionally used to perform a function later.
See for yourself:
redux-thunk can also wrap calculations that might be slow, or even unending, while other code components can decide whether to actually run the thunk.
The key benefit provided by redux-thunk is it allows us to avoid directly causing side effects in our actions, action creators, or components. Potentially messy code can be isolated in a thunk, leaving the rest of the code uncluttered. Middleware can later invoke the thunk to actually execute that function. Employing a code structure of this nature makes it easier to test, maintain, extend, and reuse all components in a given codebase.
But, while redux-thunk works well for simple use cases, it may struggle to handle more complex scenarios. This brings us to…
redux-saga is a different kind of middleware which can be used to handle asynchronous executions, and is an alternative to redux-thunk which comes with the added benefit of being able to easily handle complicated scenarios. redux-saga works by listening for dispatched actions, performing side effects, and dispatching actions to be handled by the redux reducer.
Because redux-saga relies on ES6 Generator functions, its code is simpler and more readable. However, asynchronous calls which would normally be directly inside an action creator in redux-thunk will have a clear separation in redux-saga.
The benefits of using redux-saga are many. Testing is much easier. The code is much more readable and test cases become simple without needing to mock asynchronous behaviour, thus making redux-saga a great fit to handle complex scenarios. However, redux-saga also brings in a lot of added complexity and additional dependencies.
redux-observable is the new kid on the block and can accomplish pretty much everything redux-saga can. Both are middleware. But, the difference between the two stems from how they operate— redux-saga uses the generator function, while redux-observable doesn’t.
redux-observable relies on ‘epic’, which is a function that takes a stream of actions and returns a modified stream of actions. You can think of an epic as a description of what additional actions redux-observable should dispatch. An epic is very much similar to the concept of a “saga” in redux-saga.
The benefits of redux-observable lie in its high function reusability and easy testing. However, in contrast to redux-saga, tests in redux-observable require mocking.
Which One Should I Use?
This is where things get tricky. As a rule, don’t bring in redux-saga or redux-observable before you need them. For most simple cases, redux-thunk will serve you well (and is very easy to learn, too). As the async becomes more complex, that’s when you should start thinking about bringing in redux-saga or redux-observable, because that is where they truly shine.
And how should you choose between redux-saga and redux-observable?
That, young padawan, is a balancing act. Our advice is to weigh up the pros and cons, and go with the one that promises a higher marginal benefit.