Redux is a great way to manage your state in any React application. To learn the basics of Redux, visit the Redux website .
A very simple Redux application looks something like this
Reducers ──────────────────────────▶ React (View)
▲ │
│ │
│ │
│ │
│ ▼
└─────────────────────────────── Actions
(onClick, onSumbit, etc.)
This setup works for really simple applications, but fails to provide the ability to make API calls or use websockets, timers, etc. Where are you suppose to put all that? Luckily, Redux has a feature called middleware. Redux middleware is a function that has the following signature.
store => next => action => undefined
In ES5 it would a function like this
function (store) {
return function(next) {
return function(action) {
// Do something with each action
};
}
}
It sits between the actions and the reducers in the diagram.
Reducers ──────────────────────────▶ React (View)
▲ │
│ │
│ │
│ │
│ ▼
middleware ◀─────────────────────── Actions
(onClick, onSumbit, etc.)
In the function definition, the store
is the instance of the redux store. The next
is the next middleware in the chain and the action
is the action sent by the action creater. Get more info here
Middleware allows you to do some really powerful things. For instance, you can return a Promise
as an action and then create middleware to wait for the Promise to resolve and send the data as an action. That is how the redux-promise middleware works. Another middleware example is redux-thunk, which is similar to redux-promise, but uses callbacks instead of Promises.
Both of those libraries are great and a lot of redux developers use them, but there are still some problems. How do you cancel actions, or make sure actions are in order? In an autocomplete the user may type multiple keys quickly and API responses can come back in the incorrect order.
Or what about timers? Maybe you want to show a message and have it disappear after 10 seconds, but the user is able to close the message box before the 10 seconds finish. redux-promise and redux-thunk don’t work well for those use cases. Some developers write the timers in their React views, but that has issues as well.
There is a better way! redux-observable is the better way. redux-observable is a middleware, just like the ones I described earlier, except it uses RxJS Observables instead of Promises/callbacks. If you are not familiar with Observables, that is OK. You can see some of the features in this article and then get more details later. Observables are incredibly powerful. They allow you to use all the operations you use on arrays (map, filter, reduce, etc.) on events over time, or in our case, Redux actions over time. When React creates Redux actions, we can change the actions and create new actions.
This is the diagram with redux-observable.
Reducers ──────────────────────────▶ React (View)
▲ │
│ │
│ │
│ │
│ ▼
Observables ◀────────────────────── Actions
▲ │ (onClick, onSumbit, etc.)
│ ▼
◀─────
There is a loop on Observables because Observables can trigger other Observables in a loop. With redux-observable, your actions are just plain javascript objects. Just like the original diagram. They are not functions or Promises like with redux-promise/redux-thunk. You create Observables that interact with the Javascript objects (actions) and do all the heavy lifting of ajax, timers, etc.
Here is what the code for a simple redux-observable epic
looks like. An epic
is a function that takes an Observable of Redux actions and returns a new Observable. Let’s take
a look at an example.
const mapEpic = (action$) =>
action$.ofType('SOME_ACTION').map((action) => {
return {
type: 'ANOTHER_ACTION',
payload: action.payload
};
});
In the above epic
, we filter for actions with type 'SOME_ACTION'
using the .ofType
and then use .map
(exactly like with arrays) to change the action to 'ANOTHER_ACTION'
. ofType
and map
are RxJS operators. A full list can be found on the RxJS website.
Just like the reducers in Redux, every epic
will receive all the actions. That is why the ofType
is necessary. The dollar sign at the end of action$
is just a naming convention for Observables. It is like the convention for jquery elements $el
.
The 'ANOTHER_ACTION'
will get passed to the reducers after the original 'SOME_ACTION'
.
Now for something a little bit more powerful. Let’s make the autocomplete from earlier work correctly by only rendering the most current ajax call.
import { ajax } 'rxjs/observable/dom/ajax';
const apiEpic = (action$) =>
action$.ofType('AJAX_CALL').switchMap((action) =>
ajax({url: '/some-data'}).map((result) => {
const data = JSON.parse(result.response);
return {
type: 'AJAX_CALL_RESPONSE',
payload: data
};
})
)
The above code uses an ajax
function from RxJS. It is very similar to the fetch
API except it returns an Observable instead of a Promise. The switchMap
function is the important piece. switchMap
will discard the old request if a new 'AJAX_CALL'
action comes in before the API responds. switchMap
is another RxJS operator.
Now lets see how to do the message timer with cancel.
import { of } from 'rxjs/observable/of';
const messageEpic = (action$) => {
const cancelMessages$ = action$.ofType('MESSAGE_END');
return action$.ofType('MESSAGE').mergeMap((action) =>
of({ type: 'MESSAGE_END' })
.delay(10000)
.takeUntil(cancelMessages$);
};
This epic
is a little more complicated. We listen for type 'MESSAGE'
. For each one, we create an Observable of
type 'MESSAGE_END'
and delay it 10000 millis (10 seconds). takeUntil
means ignore the actions/events if another Observable emits a value. In this case, don’t send a 'MESSAGE_END'
if the user sends a 'MESSAGE_END'
before the 10 seconds are done. mergeMap
is necessary instead of map
because of
creates an Observable and we want to merge (or flatten) the Observable. Similar to flatMap with arrays.
Hopefully I have peaked your interest a little. redux-observable has made my UI development a lot easier. To learn more about redux-observable, head over to the redux-observable github page.