Epic Middleware in Redux
RxJS and redux-observable
Middleware is a powerful feature of Redux applications that “provides a third-party extension point between dispatching an action, and the moment it reaches the reducer”. This means that you can access dispatched actions as well as the store, and then do things like impure, asynchronous tasks (such as talking to the server) and optionally dispatch new actions.
Redux-observable is middleware for Redux that uses RxJS under the hood to turn every action emitted by your Redux app into an observable stream. The core primitive of redux-observable is called an
epic, which is a function that listens for actions on that stream, and allows you to react to those actions before optionally emitting new actions yourself — actions in , actions out. The middleware does this by calling
.subscribe(store.dispatch) for you behind the scenes on every item emitted by your root epic. Here’s an example of what an epic might look like:
at the highest level, epics are “actions in, actions out”
Epics are “long-lived” — they start up when your Redux app is initialized, and continue running until your app terminates. Even though redux-observable is middleware, it might be easier to think of epics as running in parallel to everything in the normal redux cycle. Here’s one way to visualize that flow:
In this flow, epics can’t swallow actions, or prevent or modify anything within the redux lifecycle. This means that dispatching an action from your UI will always reach the reducers. In fact, by the time an action reaches your epics, it’s already been run through your reducers. Epics are just listening for all actions, filtering the ones they care about, doing some side effect-y things, then optionally emitting any new actions you want to dispatch.
Ok, so, what’s a practical example of solving a real problem with epics?!
How about autosave.
Imagine you have an input field, and every time a user types something into that field you’d like to debounce their keystrokes, show some saving UI, make a call to the server, and then show some other UI to confirm that the save was successful, or that there was an error.
Leaning on redux-observable middleware, we can achieve all of this functionality in barely 13 lines by using just a handful of Rx operators. If we include the imports, we’re looking at about 29 lines in total:
I think it’s pretty powerful (and really cool) to have one composed stream that handles all of these concerns so tersely, while fully handling success and error scenarios. 👌🏻
In the next post I’m going to talk about how we can go about writing unit tests for this!