Don't initialize reconcile transaction on server
...when calling setState from within a componentWillMount. Fixes #1866. Test Plan: jest
Ben Alpert committed
Jul 18, 2014 at 10:58 UTC
54d91c293d39ea82a84f7c9ed4e492ff6c72a811
2 files changed
+45
-3
src/browser/server/__tests__/ReactServerRendering-test.js
+26
-3
@@ -31,12 +31,13 @@ require('mock-modules')
31
32
var mocks = require('mocks');
33
34
+var ExecutionEnvironment;
35
var React;
36
+var ReactMarkupChecksum;
37
var ReactMount;
38
+var ReactReconcileTransaction;
39
var ReactTestUtils;
40
var ReactServerRendering;
38
-var ReactMarkupChecksum;
39
-var ExecutionEnvironment;
41
42
var ID_ATTRIBUTE_NAME;
43
@@ -44,12 +45,14 @@ describe('ReactServerRendering', function() {
45
beforeEach(function() {
46
require('mock-modules').dumpCache();
47
React = require('React');
48
+ ReactMarkupChecksum = require('ReactMarkupChecksum');
49
ReactMount = require('ReactMount');
50
ReactTestUtils = require('ReactTestUtils');
51
+ ReactReconcileTransaction = require('ReactReconcileTransaction');
52
+
53
ExecutionEnvironment = require('ExecutionEnvironment');
54
ExecutionEnvironment.canUseDOM = false;
55
ReactServerRendering = require('ReactServerRendering');
52
- ReactMarkupChecksum = require('ReactMarkupChecksum');
56
57
var DOMProperty = require('DOMProperty');
58
ID_ATTRIBUTE_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
@@ -373,5 +376,25 @@ describe('ReactServerRendering', function() {
376
'a valid ReactComponent.'
377
);
378
});
379
+
380
+ it('allows setState in componentWillMount without using DOM', function() {
381
+ var Component = React.createClass({
382
+ componentWillMount: function() {
383
+ this.setState({text: 'hello, world'});
384
+ },
385
+ render: function() {
386
+ return <div>{this.state.text}</div>;
387
+ }
388
+ });
389
+
390
+ ReactReconcileTransaction.prototype.perform = function() {
391
+ // We shouldn't ever be calling this on the server
392
+ throw new Error('Browser reconcile transaction should not be used');
393
+ };
394
+ var markup = ReactServerRendering.renderComponentToString(
395
+ <Component />
396
+ );
397
+ expect(markup.indexOf('hello, world') >= 0).toBe(true);
398
+ });
399
});
400
});
src/core/ReactUpdates.js
+19
@@ -172,6 +172,25 @@ var flushBatchedUpdates = ReactPerf.measure(
172
// componentDidUpdate) but we need to check here too in order to catch
173
// updates enqueued by setState callbacks.
174
while (dirtyComponents.length) {
175
+ var allUnmounted = true;
176
+ for (var i = 0, l = dirtyComponents.length; i < l; i++) {
177
+ if (dirtyComponents[i].isMounted()) {
178
+ allUnmounted = false;
179
+ break;
180
+ }
181
+ }
182
+
183
+ if (allUnmounted) {
184
+ // All the "dirty" components are unmounted, which probably means that
185
+ // they were marked dirty due to setState calls in componentWillMount
186
+ // handlers and the components are currently in the process of mounting.
187
+ // `runBatchedUpdates` will be a noop. In that case, initializing the
188
+ // DOM-dependent ReactReconcileTransaction is thus not what we want to
189
+ // do, especially when using server rendering, so we skip it.
190
+ dirtyComponents.length = 0;
191
+ return;
192
+ }
193
+
194
var transaction = ReactUpdatesFlushTransaction.getPooled();
195
transaction.perform(runBatchedUpdates, null, transaction);
196
ReactUpdatesFlushTransaction.release(transaction);