From 54d91c293d39ea82a84f7c9ed4e492ff6c72a811 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Fri, 18 Jul 2014 10:58:37 -0700 Subject: [PATCH] Don't initialize reconcile transaction on server ...when calling setState from within a componentWillMount. Fixes #1866. Test Plan: jest --- .../__tests__/ReactServerRendering-test.js | 29 +++++++++++++++++-- src/core/ReactUpdates.js | 19 ++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/browser/server/__tests__/ReactServerRendering-test.js b/src/browser/server/__tests__/ReactServerRendering-test.js index 895ce51238e..6af97d25021 100644 --- a/src/browser/server/__tests__/ReactServerRendering-test.js +++ b/src/browser/server/__tests__/ReactServerRendering-test.js @@ -31,12 +31,13 @@ require('mock-modules') var mocks = require('mocks'); +var ExecutionEnvironment; var React; +var ReactMarkupChecksum; var ReactMount; +var ReactReconcileTransaction; var ReactTestUtils; var ReactServerRendering; -var ReactMarkupChecksum; -var ExecutionEnvironment; var ID_ATTRIBUTE_NAME; @@ -44,12 +45,14 @@ describe('ReactServerRendering', function() { beforeEach(function() { require('mock-modules').dumpCache(); React = require('React'); + ReactMarkupChecksum = require('ReactMarkupChecksum'); ReactMount = require('ReactMount'); ReactTestUtils = require('ReactTestUtils'); + ReactReconcileTransaction = require('ReactReconcileTransaction'); + ExecutionEnvironment = require('ExecutionEnvironment'); ExecutionEnvironment.canUseDOM = false; ReactServerRendering = require('ReactServerRendering'); - ReactMarkupChecksum = require('ReactMarkupChecksum'); var DOMProperty = require('DOMProperty'); ID_ATTRIBUTE_NAME = DOMProperty.ID_ATTRIBUTE_NAME; @@ -373,5 +376,25 @@ describe('ReactServerRendering', function() { 'a valid ReactComponent.' ); }); + + it('allows setState in componentWillMount without using DOM', function() { + var Component = React.createClass({ + componentWillMount: function() { + this.setState({text: 'hello, world'}); + }, + render: function() { + return
{this.state.text}
; + } + }); + + ReactReconcileTransaction.prototype.perform = function() { + // We shouldn't ever be calling this on the server + throw new Error('Browser reconcile transaction should not be used'); + }; + var markup = ReactServerRendering.renderComponentToString( + + ); + expect(markup.indexOf('hello, world') >= 0).toBe(true); + }); }); }); diff --git a/src/core/ReactUpdates.js b/src/core/ReactUpdates.js index 9f8f9bf89e7..759e814ae08 100644 --- a/src/core/ReactUpdates.js +++ b/src/core/ReactUpdates.js @@ -172,6 +172,25 @@ var flushBatchedUpdates = ReactPerf.measure( // componentDidUpdate) but we need to check here too in order to catch // updates enqueued by setState callbacks. while (dirtyComponents.length) { + var allUnmounted = true; + for (var i = 0, l = dirtyComponents.length; i < l; i++) { + if (dirtyComponents[i].isMounted()) { + allUnmounted = false; + break; + } + } + + if (allUnmounted) { + // All the "dirty" components are unmounted, which probably means that + // they were marked dirty due to setState calls in componentWillMount + // handlers and the components are currently in the process of mounting. + // `runBatchedUpdates` will be a noop. In that case, initializing the + // DOM-dependent ReactReconcileTransaction is thus not what we want to + // do, especially when using server rendering, so we skip it. + dirtyComponents.length = 0; + return; + } + var transaction = ReactUpdatesFlushTransaction.getPooled(); transaction.perform(runBatchedUpdates, null, transaction); ReactUpdatesFlushTransaction.release(transaction);