Sometimes you need to cancel a pending request based on an action performed by the User. In this lesson we’ll see how we can stop an in-flight ajax request by dispatching another action into the store and making use of the Rx operator ‘takeUntil’.
This application makes API requests based on what is typed in this field. Now, if we wanted to implement a feature that allowed a user to stop a pending request, for example, if they go into a really low connectivity or they lose Internet connection altogether, the loading state that the app goes into may not be desirable for the user.
Let's simulate a really slow response first and then we'll look at how to implement the cancellation. On this Ajax call here, we can just delay this by five seconds just to make this clearer in the browser. Now, if we do that same search, we go into this loading state. Eventually the results come in.
We're going to add a cancel button that shows when we're in a loading state and clicking it will stop the results from ever occurring on the screen. The first thing we'll do is let's move this delay out of here because that's a fake delay, and I don't want that to get in the way of explaining how we're going to handle this. We put the delay up there so we can sort of forget about it.
Now to implement the cancellation, directly after this Ajax call, we can say take until. Then inside here, we get to provide an observable. We can say actions of type. Then we'll call it cancel search. We'll create this constant in a moment.
First let's look at how take until works. This operator will accept elements from the stream before it only up until the stream you give it as an argument produces a value. Once this does produce a value, which in our case will be clicking on the cancel button, take until will unsubscribe to the previous observable.
What that means in practice is that because we have that fake five second delay, should we click before the five seconds up this map function will never run, and we'll never get the received beers which is like the success action being dispatched into the Redux store. On its own, that's enough to cancel the Ajax request.
A couple of things to note here is the reason we use something like take until is that it handles the subscription and the removal of the subscription for us. This removes the need for us to manually call subscribe which is more of an imperative style of programming and can lead to way more bugs.
It also handles the subscription and the removal of the subscription for this stream that you're passing in here. We don't need to worry that this is just going to be running throughout the life cycle of the app, but this will only take effect while ever this stream is in action.
Finally, probably the most interesting part is because this takes an observable as the trigger, you can remove that, and you can build up for example a merged observable.
Let's say you not only wanted to cancel the stream when a button was clicked but also when you navigate from page to page. This is a really common problem. You could say that blockers is equal to observable.merge. Then we can paste that one in there, and we can just add as many observable streams in here as we like. Let's say we had a navigate action. We could keep adding them in.
Because merge will produce the elements from all of the streams that you give it, it's like they're all racing. The first one that happens will cause the Ajax to stop. We can just pass in blocks here, and that's how we would do it.
Back to our simpler example, though. We now need to go ahead and create this cancel search constant. As always, we'll create a function that can generate the action for us. We don't need a payload, and we can get rid of the parameter. Now we have this action that we can call from anywhere in the app, have the ability to cancel any search that's currently active.
Let's first go into the app component and pass through the loading state from this.props.loading. Then we'll also pass through that cancel method. We'll just call it cancel there. We'll say this.props.cancel search. Now we need to pass this function in to our map dispatch object. Then we can import that function. Now we've added the loading parameter and the cancel function to this search. We can destructure. We've got loading and cancel.
Now let's add a button only when we're in a loading state. We'll say loading. Then when we click it, we want to call cancel. Let's go back to the Epic and import that cancel search constant that we just created. Then we can go and test it out in the browser. Remember we have that fake five second delay.
If I type in S-K-U, you get the cancel button. If I click that, the loading spinner stays there but notice we don't get any results here. If we check the Redux dev tools, you can see that we went into a loading state and then we canceled.
All we need to do now is make sure that this loading spinner disappears when it's been canceled. If we go back into the reducer, we can add another case, this time responding to cancel search. All we're going to do there is set the loading state back to false.
If we save that, try again. We try a search, hit cancel, loading state is gone, and we're back to where we were. Just to make sure this is working, we'll leave it for the five seconds, and we get the results we expected.