Skip to content

Commit 12b532c

Browse files
committed
Add ReactUpdates.setImmediate for async callbacks
Callbacks passed to this setImmediate function are called at the end of the current update cycle, which is guaranteed to be asynchronous but in the same event loop (with the default batching strategy). This is useful for new-style refs (facebook#1373, facebook#1554) and for fixing facebook#1698. Test Plan: jest
1 parent 520d73d commit 12b532c

File tree

2 files changed

+109
-7
lines changed

2 files changed

+109
-7
lines changed

src/core/ReactUpdates.js

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ var mixInto = require('mixInto');
2929
var warning = require('warning');
3030

3131
var dirtyComponents = [];
32+
var setImmediateCallbackQueue = CallbackQueue.getPooled();
33+
var setImmediateEnqueued = false;
3234

3335
var batchingStrategy = null;
3436

@@ -73,7 +75,7 @@ var TRANSACTION_WRAPPERS = [NESTED_UPDATES, UPDATE_QUEUEING];
7375
function ReactUpdatesFlushTransaction() {
7476
this.reinitializeTransaction();
7577
this.dirtyComponentsLength = null;
76-
this.callbackQueue = CallbackQueue.getPooled(null);
78+
this.callbackQueue = CallbackQueue.getPooled();
7779
this.reconcileTransaction =
7880
ReactUpdates.ReactReconcileTransaction.getPooled();
7981
}
@@ -170,11 +172,21 @@ var flushBatchedUpdates = ReactPerf.measure(
170172
// ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
171173
// array and perform any updates enqueued by mount-ready handlers (i.e.,
172174
// componentDidUpdate) but we need to check here too in order to catch
173-
// updates enqueued by setState callbacks.
174-
while (dirtyComponents.length) {
175-
var transaction = ReactUpdatesFlushTransaction.getPooled();
176-
transaction.perform(runBatchedUpdates, null, transaction);
177-
ReactUpdatesFlushTransaction.release(transaction);
175+
// updates enqueued by setState callbacks and setImmediate calls.
176+
while (dirtyComponents.length || setImmediateEnqueued) {
177+
if (dirtyComponents.length) {
178+
var transaction = ReactUpdatesFlushTransaction.getPooled();
179+
transaction.perform(runBatchedUpdates, null, transaction);
180+
ReactUpdatesFlushTransaction.release(transaction);
181+
}
182+
183+
if (setImmediateEnqueued) {
184+
setImmediateEnqueued = false;
185+
var queue = setImmediateCallbackQueue;
186+
setImmediateCallbackQueue = CallbackQueue.getPooled();
187+
queue.notifyAll();
188+
CallbackQueue.release(queue);
189+
}
178190
}
179191
}
180192
);
@@ -221,6 +233,20 @@ function enqueueUpdate(component, callback) {
221233
}
222234
}
223235

236+
/**
237+
* Enqueue a callback to be run at the end of the current batching cycle. Throws
238+
* if no updates are currently being performed.
239+
*/
240+
function setImmediate(callback, context) {
241+
invariant(
242+
batchingStrategy.isBatchingUpdates,
243+
'ReactUpdates.setImmediate: Can\'t enqueue an immediate callback in a ' +
244+
'context where updates are not being batched.'
245+
);
246+
setImmediateCallbackQueue.enqueue(callback, context);
247+
setImmediateEnqueued = true;
248+
}
249+
224250
var ReactUpdatesInjection = {
225251
injectReconcileTransaction: function(ReconcileTransaction) {
226252
invariant(
@@ -259,7 +285,8 @@ var ReactUpdates = {
259285
batchedUpdates: batchedUpdates,
260286
enqueueUpdate: enqueueUpdate,
261287
flushBatchedUpdates: flushBatchedUpdates,
262-
injection: ReactUpdatesInjection
288+
injection: ReactUpdatesInjection,
289+
setImmediate: setImmediate
263290
};
264291

265292
module.exports = ReactUpdates;

src/core/__tests__/ReactUpdates-test.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,4 +748,79 @@ describe('ReactUpdates', function() {
748748
React.renderComponent(<A x={2} />, container);
749749
expect(callbackCount).toBe(1);
750750
});
751+
752+
it('calls setImmediate callbacks properly', function() {
753+
var callbackCount = 0;
754+
var A = React.createClass({
755+
render: function() {
756+
return <div />;
757+
},
758+
componentDidUpdate: function() {
759+
var component = this;
760+
ReactUpdates.setImmediate(function() {
761+
expect(this).toBe(component);
762+
callbackCount++;
763+
ReactUpdates.setImmediate(function() {
764+
callbackCount++;
765+
});
766+
expect(callbackCount).toBe(1);
767+
}, this);
768+
expect(callbackCount).toBe(0);
769+
}
770+
});
771+
772+
var container = document.createElement('div');
773+
var component = React.renderComponent(<A />, container);
774+
component.forceUpdate();
775+
expect(callbackCount).toBe(2);
776+
});
777+
778+
it('calls setImmediate callbacks with queued updates', function() {
779+
var log = [];
780+
var A = React.createClass({
781+
getInitialState: () => ({updates: 0}),
782+
render: function() {
783+
log.push('render-' + this.state.updates);
784+
return <div />;
785+
},
786+
componentDidUpdate: function() {
787+
if (this.state.updates === 1) {
788+
ReactUpdates.setImmediate(function() {
789+
this.setState({updates: 2}, function() {
790+
ReactUpdates.setImmediate(function() {
791+
log.push('setImmediate-1.2');
792+
});
793+
log.push('setState-cb');
794+
});
795+
log.push('setImmediate-1.1');
796+
}, this);
797+
} else if (this.state.updates === 2) {
798+
ReactUpdates.setImmediate(function() {
799+
log.push('setImmediate-2');
800+
});
801+
}
802+
log.push('didUpdate-' + this.state.updates);
803+
}
804+
});
805+
806+
var container = document.createElement('div');
807+
var component = React.renderComponent(<A />, container);
808+
component.setState({updates: 1});
809+
expect(log).toEqual([
810+
'render-0',
811+
// We do the first update...
812+
'render-1',
813+
'didUpdate-1',
814+
// ...which calls a setImmediate and enqueues a second update...
815+
'setImmediate-1.1',
816+
// ...which runs and enqueues the setImmediate-2 log in its didUpdate...
817+
'render-2',
818+
'didUpdate-2',
819+
// ...and runs the setState callback, which enqueues the log for
820+
// setImmediate-1.2.
821+
'setState-cb',
822+
'setImmediate-2',
823+
'setImmediate-1.2'
824+
]);
825+
});
751826
});

0 commit comments

Comments
 (0)