-
Notifications
You must be signed in to change notification settings - Fork 154
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow injection of a batching function to wrap dispatches
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
- Loading branch information
Brandon Tilley
committed
Apr 6, 2015
1 parent
d35b604
commit 51f7651
Showing
3 changed files
with
162 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
var Fluxxor = require("../../"), | ||
jsdom = require("jsdom"); | ||
|
||
var chai = require("chai"), | ||
expect = chai.expect; | ||
|
||
var Store = Fluxxor.createStore({ | ||
actions: { | ||
"ACTIVATE": "handleActivate", | ||
"LOAD_INITIAL_VALUE": "handleLoadInitialValue" | ||
}, | ||
|
||
initialize: function() { | ||
this.activated = false; | ||
this.value = null; | ||
}, | ||
|
||
handleActivate: function() { | ||
this.activated = true; | ||
this.emit("change"); | ||
}, | ||
|
||
handleLoadInitialValue: function() { | ||
this.value = "testing"; | ||
this.emit("change"); | ||
} | ||
}); | ||
|
||
var actions = { | ||
activate: function(callback) { | ||
setTimeout(function() { | ||
try { | ||
this.dispatch("ACTIVATE"); | ||
callback(); | ||
} catch (ex) { | ||
callback(ex); | ||
} | ||
}.bind(this)); | ||
}, | ||
|
||
loadInitialValue: function() { | ||
this.dispatch("LOAD_INITIAL_VALUE"); | ||
} | ||
}; | ||
|
||
describe("Batching updates", function() { | ||
var React, TestUtils; | ||
var flux, App, ComponentA, ComponentB; | ||
|
||
beforeEach(function() { | ||
global.window = jsdom.jsdom().createWindow("<html><body></body></html>"); | ||
global.document = window.document; | ||
global.navigator = window.navigator; | ||
React = require("react/addons"); | ||
TestUtils = React.addons.TestUtils; | ||
|
||
flux = new Fluxxor.Flux({store: new Store()}, actions); | ||
|
||
App = React.createClass({ | ||
mixins: [Fluxxor.FluxMixin(React), Fluxxor.StoreWatchMixin("store")], | ||
|
||
getStateFromFlux: function() { | ||
return { | ||
activated: this.getFlux().store("store").activated | ||
}; | ||
}, | ||
|
||
render: function() { | ||
if (!this.state.activated) { | ||
return React.createElement(ComponentA); | ||
} else { | ||
return React.createElement(ComponentB); | ||
} | ||
} | ||
}); | ||
|
||
ComponentA = React.createClass({ | ||
mixins: [ | ||
Fluxxor.FluxMixin(React) | ||
], | ||
|
||
render: function() { | ||
return React.DOM.div(); | ||
} | ||
}); | ||
|
||
ComponentB = React.createClass({ | ||
mixins: [ | ||
Fluxxor.FluxMixin(React), | ||
Fluxxor.StoreWatchMixin("store") | ||
], | ||
|
||
getStateFromFlux: function() { | ||
return { | ||
value: this.getFlux().store("store").value | ||
}; | ||
}, | ||
|
||
componentWillMount: function() { | ||
this.getFlux().actions.loadInitialValue(); | ||
}, | ||
|
||
render: function() { | ||
return React.DOM.div(); | ||
}, | ||
}); | ||
}); | ||
|
||
afterEach(function() { | ||
delete global.window; | ||
delete global.document; | ||
delete global.navigator; | ||
for (var i in require.cache) { | ||
if (require.cache.hasOwnProperty(i)) { | ||
delete require.cache[i]; // ugh react why | ||
} | ||
} | ||
}); | ||
|
||
it("doesn't batch by default", function(done) { | ||
/* jshint expr:true */ | ||
TestUtils.renderIntoDocument(React.createElement(App, {flux: flux})); | ||
flux.actions.activate(function(err) { | ||
expect(err).to.match(/dispatch.*another action/); | ||
done(); | ||
}); | ||
}); | ||
|
||
it("allows batching", function(done) { | ||
/* jshint expr:true */ | ||
flux.setBatchingFunction(React.addons.batchedUpdates); | ||
|
||
TestUtils.renderIntoDocument(React.createElement(App, {flux: flux})); | ||
flux.actions.activate(function(err) { | ||
expect(err).to.be.undefined; | ||
done(); | ||
}); | ||
}); | ||
}); |