Skip to content

Commit 3e2182f

Browse files
committed
Merge pull request #1231 from spicyj/gh-1227
Batch updates caused by handlers in multiple roots
2 parents 3bf12bb + 933dde9 commit 3e2182f

File tree

3 files changed

+59
-13
lines changed

3 files changed

+59
-13
lines changed

src/browser/ui/ReactEventTopLevelCallback.js

+21-10
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var PooledClass = require('PooledClass');
2323
var ReactEventEmitter = require('ReactEventEmitter');
2424
var ReactInstanceHandles = require('ReactInstanceHandles');
2525
var ReactMount = require('ReactMount');
26+
var ReactUpdates = require('ReactUpdates');
2627

2728
var getEventTarget = require('getEventTarget');
2829
var mixInto = require('mixInto');
@@ -56,13 +57,11 @@ function findParent(node) {
5657
* ancestor list. Separated from createTopLevelCallback to avoid try/finally
5758
* deoptimization.
5859
*
59-
* @param {string} topLevelType
60-
* @param {DOMEvent} nativeEvent
6160
* @param {TopLevelCallbackBookKeeping} bookKeeping
6261
*/
63-
function handleTopLevelImpl(topLevelType, nativeEvent, bookKeeping) {
62+
function handleTopLevelImpl(bookKeeping) {
6463
var topLevelTarget = ReactMount.getFirstReactDOM(
65-
getEventTarget(nativeEvent)
64+
getEventTarget(bookKeeping.nativeEvent)
6665
) || window;
6766

6867
// Loop through the hierarchy, in case there's any nested components.
@@ -79,24 +78,31 @@ function handleTopLevelImpl(topLevelType, nativeEvent, bookKeeping) {
7978
topLevelTarget = bookKeeping.ancestors[i];
8079
var topLevelTargetID = ReactMount.getID(topLevelTarget) || '';
8180
ReactEventEmitter.handleTopLevel(
82-
topLevelType,
81+
bookKeeping.topLevelType,
8382
topLevelTarget,
8483
topLevelTargetID,
85-
nativeEvent
84+
bookKeeping.nativeEvent
8685
);
8786
}
8887
}
8988

9089
// Used to store ancestor hierarchy in top level callback
91-
function TopLevelCallbackBookKeeping() {
90+
function TopLevelCallbackBookKeeping(topLevelType, nativeEvent) {
91+
this.topLevelType = topLevelType;
92+
this.nativeEvent = nativeEvent;
9293
this.ancestors = [];
9394
}
9495
mixInto(TopLevelCallbackBookKeeping, {
9596
destructor: function() {
97+
this.topLevelType = null;
98+
this.nativeEvent = null;
9699
this.ancestors.length = 0;
97100
}
98101
});
99-
PooledClass.addPoolingTo(TopLevelCallbackBookKeeping);
102+
PooledClass.addPoolingTo(
103+
TopLevelCallbackBookKeeping,
104+
PooledClass.twoArgumentPooler
105+
);
100106

101107
/**
102108
* Top-level callback creator used to implement event handling using delegation.
@@ -135,9 +141,14 @@ var ReactEventTopLevelCallback = {
135141
return;
136142
}
137143

138-
var bookKeeping = TopLevelCallbackBookKeeping.getPooled();
144+
var bookKeeping = TopLevelCallbackBookKeeping.getPooled(
145+
topLevelType,
146+
nativeEvent
147+
);
139148
try {
140-
handleTopLevelImpl(topLevelType, nativeEvent, bookKeeping);
149+
// Event queue being processed in the same cycle allows
150+
// `preventDefault`.
151+
ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
141152
} finally {
142153
TopLevelCallbackBookKeeping.release(bookKeeping);
143154
}

src/browser/ui/__tests__/ReactEventTopLevelCallback-test.js

+37
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,43 @@ describe('ReactEventTopLevelCallback', function() {
117117
expect(calls[0][EVENT_TARGET_PARAM]).toBe(childNode);
118118
expect(calls[1][EVENT_TARGET_PARAM]).toBe(parentControl.getDOMNode());
119119
});
120+
121+
it('should batch between handlers from different roots', function() {
122+
var childContainer = document.createElement('div');
123+
var parentContainer = document.createElement('div');
124+
var childControl = ReactMount.renderComponent(
125+
<div>Child</div>,
126+
childContainer
127+
);
128+
var parentControl = ReactMount.renderComponent(
129+
<div>Parent</div>,
130+
parentContainer
131+
);
132+
parentControl.getDOMNode().appendChild(childContainer);
133+
134+
// Suppose an event handler in each root enqueues an update to the
135+
// childControl element -- the two updates should get batched together.
136+
var childNode = childControl.getDOMNode();
137+
ReactEventEmitter.handleTopLevel.mockImplementation(
138+
function(topLevelType, topLevelTarget, topLevelTargetID, nativeEvent) {
139+
ReactMount.renderComponent(
140+
<div>{topLevelTarget === childNode ? '1' : '2'}</div>,
141+
childContainer
142+
);
143+
// Since we're batching, neither update should yet have gone through.
144+
expect(childNode.textContent).toBe('Child');
145+
}
146+
);
147+
148+
var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
149+
callback({
150+
target: childNode
151+
});
152+
153+
var calls = ReactEventEmitter.handleTopLevel.mock.calls;
154+
expect(calls.length).toBe(2);
155+
expect(childNode.textContent).toBe('2');
156+
});
120157
});
121158

122159
it('should not fire duplicate events for a React DOM tree', function() {

src/core/ReactEventEmitterMixin.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
"use strict";
2020

2121
var EventPluginHub = require('EventPluginHub');
22-
var ReactUpdates = require('ReactUpdates');
2322

2423
function runEventQueueInBatch(events) {
2524
EventPluginHub.enqueueEvents(events);
@@ -49,8 +48,7 @@ var ReactEventEmitterMixin = {
4948
nativeEvent
5049
);
5150

52-
// Event queue being processed in the same cycle allows `preventDefault`.
53-
ReactUpdates.batchedUpdates(runEventQueueInBatch, events);
51+
runEventQueueInBatch(events);
5452
}
5553
};
5654

0 commit comments

Comments
 (0)