In this lesson we’ll learn exactly what’s needed to incorporate redux-observable into an existing React/Redux application. We’ll learn what ‘Epics’ are, and how they receive a stream of actions and return a stream of actions. In-between is where we get to apply side-effects, call out to external services etc etc. In this lesson we’ll demonstrate the ‘async’ nature of epics by responding to an action with another, delay action.
We have a React and Redux application here. Now, we want to add Redux Observable to handle the asynchronous parts of our application. The first thing we need to do is install it along with RxJS. Redux Observable is the middleware to that hooks into Redux, but all of it's functionality is powered by RxJS. That's why you need both.
Now, Redux Observable is a Redux middleware, so we have to configure the store to use it. We'll bring in a function from Redux called applyMiddleware. Then from Redux Observable, we'll bring createEpicMiddleware.
As the name suggests, this function takes in some Epics and it generates some Redux middleware. We can say that Epic middleware is the result of calling this function and passing in a root Epic. We'll create that in a moment.
First, finish off the Redux configuration. We can pass a second argument to createStore. As this is a middleware, we'll applyMiddleware from Redux and pass in the Epic middleware. That's enough configuration for Redux. We're just left with this root Epic that we need to create.
Let's create another directory. Call it "Epics". In there, we'll have an index.js. We'll need to import Observable from RxJS and the combineEpics function from Redux Observable. You'll see why in a moment.
Now, an Epic is simply a function that takes in a stream of actions and returns a stream of actions. In your application you'll end up with many Epics. You define one for each action in the Redux store that you want to react to.
For example, in this application we have the stories component. When someone clicks on this button, we call this loadStories function that dispatches an action that has a type of LOAD_STORIES. If we want to be notified when Redux has already processed this action, then we would create the following Epic.
We'd call it something like loadStoriesEpic. That receives a stream of actions. Now, from this function we need to return our own stream of actions that we want Redux Observable to dispatch into the Redux store on our behalf.
To fully understand this, let's first just log out everything from this action stream. Because we're in Rx land here, we get a chance to use operators such as do, which is a way of observing a sequence without producing any extra elements.
We can just say console.log(action). From this stream, we're going to ignore any elements. This is just to stop the browser from locking up, because if we were to return the actions produced by this, we would go into an infinite loop.
That's enough for our first Epic. If you remember back to the index.js, we had this concept of a root Epic. Let's create that. We're going to export const rootEpic is equal to combineEpics. We use this combineEpics function from Redux Observable, as it allows us to register multiple functions.
Now, we have our root Epic export. Let's go back here, import it, hit save. Now, if we go to the browser, we would expect that clicking here or here would produce a console log. That's what we see. We have a type of LOAD_STORIES, and then a type of CLEAR_STORIES.
Now, what if we only wanted to respond to this LOAD_STORIES action, and dismiss any CLEAR_STORIES? Because we're in Rx land again, we can provide a filter here. This will receive each action. We can provide the predicate of action.type is equal to LOAD_STORIES.
Now, if we save that and head back to the browser, you can see that we still get this log here. When we click clear, we don't get it anymore. This is how we selectively respond to particular actions.
Redux Observable actually has a shortcut for this. Here we're just using the regular RxJS filter operator. Redux Observable has an ofType, in which we can just provide the name of the action, like that.
Just to be clear, if we have a quick look at the implementation of this, you can see that ofType is indeed just using a regular RxJS filter, and it accepts multiple types. If we head back to the browser again, we still got this log here for load stories and nothing for clear.
Now, let's finish this lesson off by looking at how we can take this filtered stream of actions here and produce our own actions that will be dispatched into the Redux store. Let's react to this LOAD_STORIES action by first waiting for two seconds, and then clearing the stories. Let's get rid of these lines. Then we'll do a switchMap.
To this we need to return an Observable. If we return Observable.of, and we have the Clear function here that produces the action. We'll call that function Clear. Import it. Let's delay that for two seconds. Hit save, and head to the browser.
Now when we click here, we see these. After two seconds, they disappear. It's quite incredible what we've been able to accomplish there with just a couple lines of code. Every time the LOAD_STORIES action has been processed by the Redux store, we'll receive that action in this stream.
Every time it occurs, switchMap will execute this function here, and we returned a delayed Observable. If it sees another event of this type before this delay is completed, switchMap will handle unsubscribing from the first one, and we'll resubscribe to the next one.
We kind of get like a debounce for free by this, meaning I can click this as many times as I want. Only when I stop clicking will it begin waiting for two seconds, and then clearing it.
I experienced that. My src/epics/index.js file looks like this and it is working:
import {Observable} from 'rxjs'
import {combineEpics} from 'redux-observable'
// import individual rxjs operators
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/ignoreElements';
function loadStoriesEpic(action$, store) {
return action$
.do(action => console.log(action))
.ignoreElements()
}
export const rootEpic = combineEpics(loadStoriesEpic)
The redux-observable docs talk about it here https://redux-observable.js.org/docs/Troubleshooting.html
return Observable.of(clear()).delay(2000);
returns an error even with the new imports above.
TypeError: Object(...) is not a function
You need to also add.
import 'rxjs/add/operator/delay';
Do you have a shortcut to open source code on Github on PhpStorm or was that opened manually? Specifically when you did the showing of ofType
.
Using the latest rxjs, all the operators need to be imported statically, as well as rjx/Observable. Once this has been done the errors are not an issue. This course has not yet been updated to reflect that.
Along with installing all operators individually, I found that I had to use rxjs-compat to get them to be recognized. rxjs-compat
is in the project package.json
, just not mentioned in the tutorial
also found that, following the instructions in this link: https://redux-observable.js.org/MIGRATION.html#setting-up-the-middleware
I was able to fix an error I was getting, trying to pass the rootEpic
into const epicMiddleware = createEpicMiddleware();
As the link describes, you should not pass the epic into the middleware creation function, rather, after the store is defined run something like this epicMiddleware.run(rootEpic);
First of all – this lesson without being updated is a pain to pass. So many things that can go wrong. One can think that he solved the error, when the next one happens.
IMO it's better to use the pipeable operators that they mentioned: https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md
First look at the comment that PCR IT Training made - about running the middleware instead of passing a rootEpic
into the createEpicMiddleware
function.
In the src/epics
you can import all of the operators (not recommended for production) using:
import * as operators from 'rxjs/operators'
And then destructure them:
const { tap, ignoreElements } = operators;
And yes - do
being the reserver keyword is now tap
.
This one might be also useful: https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/migration.md#import-paths
import {Observable, of} from 'rxjs'; import {combineEpics} from 'redux-observable'; import {LOAD_STORIES} from '../actions'; import {clear} from '../actions';
import 'rxjs/add/operator/do'; import 'rxjs/add/operator/ignoreElements'; import 'rxjs/add/operator/filter'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/delay';
function loadStoriesEpic(action$) { return action$.ofType(LOAD_STORIES) .switchMap(() => { return of(clear()).delay(2000); }) }
export const rootEpic = combineEpics(loadStoriesEpic);
Statc Methods of Observable have been removed So Observable.of no longer works with rxjs 6
hi, just noticed that this lesson may need to be updated. redux-observable doesnt load the operators by default and each operator like 'do', 'switchMap', 'ignoreElements' need to be loaded by a seperate import