Search code examples
react-routerrxjsrxjs5react-router-reduxredux-observable

How to cancel ajax when route changing using react router


 export const getSomethingEpic = action$ =>
  action$
    .ofType(GET_SOMETHING)
    .switchMap(action =>
      ajax('url/api/something')
        .map((json) => {
          return getSomethingEndAct(json.response);
        })
        .takeUntil(action$.ofType('@@router/LOCATION_CHANGE'))
    );

The situation is this:

page A has a react router Link to page B, on page B componentDidMount I dispatch getSomething action. Ajax can be cancelled perfectly. However, if page B loaded and the ajax finished, we route to page C from page B, and then use the browser back button to route to page B again, action getSomething will be dispatched in componentDidMount again, but this time the ajax is cancelled.

I don't understand why the ajax is being cancelled since location change is prior to getSomething action. Or what should I do to cancel ajax when route changed?

I tried to build an example, but I could not get react-router-redux to sync browser history. here is http://jsbin.com/dewixurece/edit?js,console,output it would be working if use hashHistory, but there will be two location change actions, which should be one instead and I don't know why.

it is a great lib and I really like RX programming idea, but still learning and hope we could have more examples. thanks if anyone can help.


Solution

  • (based on our discussion in the comments)

    If you want to start a request when you enter a route, but cancel it if you leave that route before it's finished, you would dispatch the start action in componentDidMount and the cancellation action in componentWillUnmount

    const getSomethingEpic = action$ =>
      action$.ofType('GET_SOMETHING')
        .switchMap(action =>
          ajax('url/api/something')
            .map((json) => {
              return getSomethingEndAct(json.response);
            })
            .takeUntil(action$.ofType('GET_SOMETHING_CANCELLED'))
        );
    
    class B extends Component {
      componentDidMount() {
        store.dispatch({
          type: 'GET_SOMETHING'
        });
      }
    
      componentWillUnmount() {
        store.dispatch({
          type: 'GET_SOMETHING_CANCELLED'
        });
      }
    
      render() {
        return (
          <h1>B route</h1>
        );
      }
    }
    

    Demo: http://jsbin.com/zomire/edit?js,output

    For simplicity sake, I just used store.dispatch directly. Use action creators and/or connect() in your real app, if you prefer that.

    Dispatching the cancellation action, even if the ajax request has already completed isn't usually a problem--but if it is or you prefer to only dispatch it if it's truly needed, you'll need to store some sort of state you can use to know whether or not the request is still pending. You can store this state either as local component state or in the redux store.