-
Notifications
You must be signed in to change notification settings - Fork 154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Trigger this.emit( 'change' );
after dispatch ends
#92
Comments
Unfortunately, this problem seems to pop up fairly often when components dispatch actions in their As you've said, one quick fix is to wrap some portion of the process in a Another pattern I've been using more often when a component needs to get initial data from a flux store is to fetch the data from the store instead of firing an action when the component mounts. If the store doesn't have the data cached/ready, it can fetch it asynchronously and then dispatch an action when it's available. Here's a basic example: https://gist.github.com/BinaryMuse/3b0a4ba166ac81840cab This allows you to remove the dispatch when the component mounts. |
Thanks for your work on Fluxxor, @BinaryMuse! I came here to report the same problem and noticed this GitHub issue. A few days ago at React.js Conference I spoke with Michael Jackson of rackt/react-router about it. His take on this (as I recall) was that actions are not meant to be long-lived in this manner. I'm sure there's a reason Fluxxor assumes the role of preventing multiple actions, but there is an obvious conflict here with an extremely common use case. (Save data on a page with an action, route to new page as a result, perform new action when the next page loads.) Using |
I made a barebones React/React-Router/Fluxxor app to recreate the error -- without luck. It didn't make actual API calls, but I simulated them using promises. Interesting that it didn't work. @BinaryMuse, would you consider allowing the dispatcher to be agnostic about multiple dispatches? I made a hacky proof-of-concept patch that isolates In an actual PR, I would make the dispatcher stateless. It would instantiate a separate "action handler" object on Thanks for considering! @mjangda, can you confirm your issue goes away after applying the patch? (It does on my project.) |
There are a few things at play here. First, not allowing cascading dispatches isn't a technical limitation; the decision to add that restriction was purposeful, based on the flux design pattern, and is even listed in the "Key Properties" on the "What is Flux" page. The restriction helps promote good data flow design by forcing you to think about your actions in terms of domain concepts rather than rote instructions your stores are following. Long story short, I'm not in favor of simply removing the cascading dispatch restriction and calling it a day. The problem, as mentioned above, is that this restriction causes exceptions when one action causes a new component to be mounted, and that new component immediately fires another action in That said, I also understand that this is a common pattern (and even implicitly recommend it in one of the examples on the site), so I think I'd like to support it if we can. The real problem here, and what makes it hard to debug, is that React renders immediately sometimes but not others. Take the following example (see it working at this JSFiddle): var App = React.createClass({
render: function() {
console.log("Rendering");
return (
<div>
<p>Value: {this.props.value}</p>
<button onClick={this.sync}>Increment Synchronously</button>
<button onClick={this.async}>Increment Asynchronously</button>
<p>
When incrementing synchronously, rendering happens on the same tick,
but after the increment function has fully finished. When incrementing
asynchronously, rendering happens immediately on the same tick,
before the increment function has finised.
</p>
</div>
);
},
sync: function() {
this.props.increment();
},
async: function() {
setTimeout(function() {
this.props.increment();
}.bind(this));
}
});
var value = 0;
var increment = function() {
console.group("Increment");
setTimeout(function() {
console.log("Next tick");
console.groupEnd("Increment");
});
console.log("Starting increment");
value++;
render();
console.log("Ended increment");
};
var render = function() {
React.render(<App value={value} increment={increment} />, document.body);
};
render(); When you click the left button, the console logging ordering is:
However, clicking the right button, the order is:
This is the problem, I think—state changes caused by event handlers allow the handlers to fully process before React re-renders, allowing the dispatch loop to finish. However, actions that are dispatched asynchronously cause an re-render immediately, and Fluxxor throws since the dispatch loop hasn't completed. So far, I've been unable to tell if this is on purpose or just a bug or side effect of the implementation used in React. I'll keep digging. |
@BinaryMuse Thanks for your quick response, for explaining the conflict, and for looking into a solution! |
Okay, thanks to monastic-panic in IRC for accidentally providing a potential solution here. Assuming you're using the latest version of React, please try the following code directly after you instantiate your // 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);
});
}; |
React automatically batches updates when inside a synthetic event handler, but asynchronous updates do not get the same treatment. Consider the following scenario: 1. A button in ComponentA calls an action creator 2. The action creator calls an async API 3. As a result of the async call, the action creator dispatches an action 4. That action sets new state in a store 5. The new store data causes a new child to be mounted inside ComponentA (let's call it ComponentB) 6. ComponentB fires an action immediately, via componentDid/WillMount Unlike re-renders from synchronous action dispatches (which generally happen in the context of a synthetic event), asynchronous dispatches aren't called within the context of React's batching strategy. `Flux#setBatchingFunction` allows you to pass a batching function to wrap dispatches in; in most (all?) cases, you'll want to pass `React.addons.batchedUpdates` in order to force all dispatches to happen in the context of React's batched updates. Fixes #92
React automatically batches updates when inside a synthetic event handler, but asynchronous updates do not get the same treatment. Consider the following scenario: 1. A button in ComponentA calls an action creator 2. The action creator calls an async API 3. As a result of the async call, the action creator dispatches an action 4. That action sets new state in a store 5. The new store data causes a new child to be mounted inside ComponentA (let's call it ComponentB) 6. ComponentB fires an action immediately, via componentDid/WillMount Unlike re-renders from synchronous action dispatches (which generally happen in the context of a synthetic event), asynchronous dispatches aren't called within the context of React's batching strategy. This means the dispatch loop is still in progress when ComponentB mounts, causing a cascading dispatch exception. `Flux#setBatchingFunction` allows you to pass a batching function to wrap dispatches in; in most (all?) cases, you'll want to pass `React.addons.batchedUpdates` in order to force all dispatches to happen in the context of React's batched updates. Fixes #92
Apologies for the delay. Give me another day or 2 on this. We're not using the latest React, so not sure if that is necessary yet. |
@paulstatezny No worries. I think |
@BinaryMuse One of my colleagues tried out your solution with |
So I tried your solution @BinaryMuse but I couldn't get it to work...I'm not sure I'm implement it correctly. I"m sorry to post this on fluxxor forum as I'm not using your repo, but I was hoping you wouldn't mind explaining a little more. I explained my issue in more detail on stack overflow...if you feel up for helping me out then I'd appreciate it, if not I understand. here's the link to my post: |
I can confirm adding the batchedUpdates code after creating the flux instance is working. Does this mean it's ok (in the flux flow) to dispatch actions while another one is dispatched? |
React automatically batches updates when inside a synthetic event handler, but asynchronous updates do not get the same treatment. Consider the following scenario: 1. A button in ComponentA calls an action creator 2. The action creator calls an async API 3. As a result of the async call, the action creator dispatches an action 4. That action sets new state in a store 5. The new store data causes a new child to be mounted inside ComponentA (let's call it ComponentB) 6. ComponentB fires an action immediately, via componentDid/WillMount Unlike re-renders from synchronous action dispatches (which generally happen in the context of a synthetic event), asynchronous dispatches aren't called within the context of React's batching strategy. This means the dispatch loop is still in progress when ComponentB mounts, causing a cascading dispatch exception. `Flux#setBatchingFunction` allows you to pass a batching function to wrap dispatches in; in most (all?) cases, you'll want to pass `React.addons.batchedUpdates` in order to force all dispatches to happen in the context of React's batched updates. Fixes #92
@BinaryMuse When is this branch scheduled to land in master and npm? Thanks. |
@ruswerner This has been published in v1.6.0 |
I'm using Fluxxor + rackt/react-router and running into the "Cannot dispatch an action (...) while another action (...) is being dispatched" error.
Here's what I'm doing:
this.emit( 'change' );
StoreWatchMixin( 'AuthStore' )
to listen for updates and uses react-router totransitionTo( 'dashboard' )
The problem is that the
emit
call triggers the transition and results in the "dashboard" component set up while the dispatcher thinks that the "LOGIN_SUCCESS" action is still being fired.Is there a way to defer the
emit
call to after the action dispatch is complete? Or should I be thinking of another approach to this? (I could do asetTimeout
but that feels fragile.)The text was updated successfully, but these errors were encountered: