How to avoid dispatching in the middle of a dispatch

20,343

Solution 1

You can use Flux waitFor() function instead of a setTimeout

For example you have 2 stores registered to the same dispatcher and have one store waitFor the other store to process the action first then the one waiting can update after and dispatch the change event. See Flux docs example

Solution 2

My particular error was occurring because my stores emitted their change event during the action dispatch, while it was still cycling through the listeners. This meant any listeners (ie components) that then triggered an action due to a data change in the store would interrupt the dispatch. I fixed it by emitting the change event after the dispatch had completed.

So this:

this.emit(CHANGE_EVENT);

Became

var self = this;

setTimeout(function() { // Run after dispatcher has finished
  self.emit(CHANGE_EVENT);
}, 0);

Still a little hacky (will probably rewrite so doesn't require a setTimeout). Open to solutions that address the architectural problem, rather than this implementation detail.

Solution 3

The reason you get a dispatch in the middle of a previous dispatch, is that your store dispatches an action (invokes an action creator) synchronously in the handler for another action. The dispatcher is technically dispatching until all its registered callbacks have been executed. So, if you dispatch a new action from either of the registered callbacks, you'll get that error.

However, if you do some async work, e.g. make an ajax request, you can still dispatch an action in the ajax callbacks, or the async callback generally. This works, because as soon as the async function has been invoked, it per definition immediately continues the execution of the function and puts the callback on the event queue.

As pointed out by Amida and in the comments of that answer, it's a matter of choice whether to make ajax requests from the store in response to an action, or whether to do it in the store. The key is that a store should only mutate its state in response to an action, not in an ajax/async callback.

In your particular case, this would be exemplified by something like this for your store's registered callback, if you prefer to make the ajax calls from the store:

onGetAll: function(options) {
    // ...do some work

    request(ajaxOptions) // example for some promise-based ajax lib
        .then(function(data) {
             getAllSuccessAction(data); // run after dispatch
        })
        .error(function(data) {
             getAllFailedAction(data); // run after dispatch
        });

     // this will be immediately run during getAllAction dispatch
     return this.state[options];
},
onGetAllSuccess: function(data) {
    // update state or something and then trigger change event, or whatever
},    
onGetAllFailed: function(data) {
    // handle failure somehow
}

Or you can just put the ajax call in your action creator and dispatch the "success/failed" actions from there.

Solution 4

you can user the "defer" option in the dispatcher. In your case it would be like:

RatingActions.fetchAll.defer(options);
Share:
20,343
Ian Walker-Sperber
Author by

Ian Walker-Sperber

Updated on July 09, 2022

Comments

  • Ian Walker-Sperber
    Ian Walker-Sperber almost 2 years

    Within my Flux architected React application I am retrieving data from a store, and would like to create an action to request that information if it does not exist. However I am running into an error where the dispatcher is already dispatching.

    My desired code is something like:

    getAll: function(options) {
      options = options || {};
      var key = JSON.stringify(options);
      var ratings = _data.ratings[key];
    
      if (!ratings) {
        RatingActions.fetchAll(options);
      }
    
      return ratings || [];
    }
    

    However intermittently fails when the dispatcher is already dispatching an action, with the message Invariant Violation: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.. I am often making requests in response to a change in application state (eg date range). My component where I make the request, in response to a change event from the AppStore has the following:

    getStateFromStores: function() {
      var dateOptions = {
        startDate: AppStore.getStartISOString(),
        endDate: AppStore.getEndISOString()
      };
    
      return {
        ratings: RatingStore.getAll(dateOptions),
      };
    },
    

    I am aware that event chaining is a Flux antipattern, but I am unsure what architecture is better for retrieving data when it does not yet exist. Currently I am using this terrible hack:

    getAll: function(options) {
      options = options || {};
      var key = JSON.stringify(options);
      var ratings = _data.ratings[key];
    
      if (!ratings) {
        setTimeout(function() {
          if (!RatingActions.dispatcher.isDispatching()) {
            RatingActions.fetchAll(options);
          }
        }, 0);
      }
    
      return ratings || [];
    },
    

    What would be a better architecture, that avoids event chaining or the dispatcher error? Is this really event chaining? I just want to change the data based on the parameters the application has set.

    Thanks!