4

Alright so I'm stuck on an issue with reactjs, flux architecture, and react-router. I have the following routes (just a portion of the routes):

<Route name="prepare-seniors">
  <Route name="senior" path=":candidateId" handler={SeniorCandidate}>
    <DefaultRoute handler={SeniorProfile}/>
    <Route name="senior-recommendations" path="recommends">
      <DefaultRoute handler={SeniorRecommends}/>
      <Route name="senior-rec-new" path="new"/>
    </Route>
  </Route>
</Route>

The Senior Profile view makes an API call to load the individual's data. When you navigate to the Recommends view, the individual's id is used to make another call to load the recommendations. It works great if I actually view the profile page first and navigate to the recommendations page. But if I do a hard reload I get:

Uncaught Error: Invariant Violation: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.

I realize this is because the dispatch is getting called after the first API returns, which goes out and starts updating components. Before it finishes the recommendations page calls its API and tries to dispatch its results. I read on a forum that React.addons.batchUpdates is a way to fix this, but I couldn't figure out how to get it to work. GitHub Batch Updates Issue and another link here that discusses something similar Trying to use waitFor. The first one recommends adjusting the dispatcher by adding the following:

// assuming a var `flux` containing your Flux instance...
var oldDispatch = flux.dispatcher.dispatch.bind(flux.dispatcher);
flux.dispatcher.dispatch = function(action) {
  React.addons.batchedUpdates(function() {
  oldDispatch(action);
  });
};

But I could not make this work. Maybe I'm just implementing it incorrectly.

I've read the Chat and TodoMVC examples. I understand how the waitFor is used in the chat example...but those both use the same API so it's clear that the one is going to wait for the other. My issue involves a race condition between APIs and dispatch...and I don't think setTimeout is a good solution.

What I need is to see how to set up the dispatcher in such a way that it will queue the dispatch or API calls. Or a better way to tell each component to make an API call for it's data, so I don't even have a dispatch issue.

Oh so here's my Dispatcher.js file so you can see how it's set up:

'use strict';

var flux = require('flux'),
 Dispatcher = require('flux').Dispatcher,
 React = require('react'),
 PayloadSources = require('../constants/PayloadSources'),
 assign = require('object-assign');

//var oldDispatcher = flux.Dispatcher.dispatch.bind(AppDispatcher);
//
//flux.dispatcher.dispatch = function(action) {
//  React.addons.batchedUpdates(function() {
//    oldDispatcher(action);
//  });
//};


var AppDispatcher = assign(new Dispatcher(), {
   handleServerAction: function(action) {
     var payload = {
       source: PayloadSources.SERVER_ACTION,
       action: action
     };
     this.dispatch(payload);
   },

   handleViewAction: function(action) {
     if (!action.type) {
       throw new Error('Empty action.type: you likely mistyped the action.');
     }

     var payload = {
       source: PayloadSources.VIEW_ACTION,
       action: action
     };
     this.dispatch(payload);
    }
});
module.exports = AppDispatcher;
BryceHayden
  • 596
  • 1
  • 6
  • 13

1 Answers1

0

Ok first I'm just going to say I am learning React and Flux at the moment and am by no means an expert. However I'm going to give it a shot anyway:

From what you have said, it sounds like you have 2 asynchronous operations triggering off and then when they return trying to send dispatch messages The issue arising from the dispatch call being triggered twice from 2 independent aync webservice calls.

I don't think batching the updates would help in this case as it would depend heavily on timing(ie. if it has queued the re-render and is waiting for the opportunity to execute when the 2nd call comes in it could batch successfully, however if it comes in mid-update you are in the exactly the same situation place you are now).

I think the actual issue here is coming from how/when these Actions are being triggered(you mention this briefly at the end of your post). It sounds like you are triggering Actions from the React component Render calls(kind of like lazy loading). This is pretty much exactly the thing Flux is trying to avoid, as that is a pattern that could easily result in an infinite loop or weird states/patterns in the flow of the application.

As you mentioned in your description you probably need to trigger this loading up front instead of from the individual components, from the little I know I would suggest that needs to be done prior to calling React.render on your core component. If you look in the example chat app they do a similar thing in app.js:

ChatExampleData.init(); // load example data into localstorage

ChatWebAPIUtils.getAllMessages();

React.render(
    <ChatApp />,
    document.getElementById('react')
);

So I would suggest following the above pattern, loading any initial data up front while showing a loading spinner to the client.

Alternatively, depending on your tech stack you could render initially on the server (node example here .Net version at ReactJS.Net) so you don't have that delay while the client side boots up(also you avoid all the funny business with search engine indexing if that is a concern (BTW I haven't tried either of these, just read about them).

Simon
  • 1
  • Yeah I noticed they were handling it that way with the chat, but I wasn't sure how that would work when the component changes based upon it's parents component state/info. I mean this works if I'm going to call the exact same API every time, but what about when the API I call varies based on the props it is given...i.e. I'm using specific data as search parameters? Sadly I can't use the server side rendering for this project as the company I'm working for didn't want to use node on the server and instead simply wants to use grunt to build static files that can then be serve up to the server – BryceHayden Feb 28 '15 at 21:49
  • Oh I was going to mention that I removed a sub-action that gets called in my actions creator, before the call to the API, and it started working. Mainly it's working because the one API is slow and I no longer have a conflict. Basically there was an action that would say data was requested from the server and set a spinner, and since the APIs are called about the same time I got the dispatch error. But this still doesn't answer my question of what to do when I have two APIs that return at around the same time, I'll still get a dispatch error. – BryceHayden Feb 28 '15 at 21:54
  • sorry last comment. I'm not trying to do it in the render method, but in the componentWillRecieveProps method, so I'm not sure if this is still an anti-pattern or not...looking at their diagram the only thing that should be hitting the Action creators from the view is "user interactions," so this is still likely wrong. – BryceHayden Feb 28 '15 at 21:59
  • Yeah, I would agree with maybe removing the action from the componentWillReceiveProps. – Simon Mar 01 '15 at 22:38
  • 1
    I just found this bit of documentation about [async loading data into a component](https://facebook.github.io/react/tips/initial-ajax.html) is that kind of what you are trying to do? If this data needs to be in a store I think you might need to push this state up higher into a parent view controller component as mentioned [here](https://facebook.github.io/react/docs/thinking-in-react.html) so that one action is generated to load all the relevant data. You might actually need to make those 2 calls dependent(as it sounds like they both contribute to the new state of the components). – Simon Mar 01 '15 at 22:50
  • Thanks for the links. I've read over them, and I'm going to apply them to my code and see how it works out. If I run into any blockers, I'll let you know. – BryceHayden Mar 02 '15 at 00:23
  • Cool, I'm keen to know as well. Also I found [this](https://groups.google.com/forum/#!topic/reactjs/eaa7IOq3zVs) in the google group. If you read I think the 3rd post down someone goes through a detailed explanation of how they are handling what I think is a similar case. Seems to be a pretty good solution. – Simon Mar 04 '15 at 23:38