Skip to content

Instantly share code, notes, and snippets.

@STRML
Created January 24, 2017 19:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save STRML/cc368c46c2d7de8679196e69ddbcaef7 to your computer and use it in GitHub Desktop.
Save STRML/cc368c46c2d7de8679196e69ddbcaef7 to your computer and use it in GitHub Desktop.
// @flow
import ReactUpdates from 'react-dom/lib/ReactUpdates';
// visibility unfortunately touches 'document' so we have to hide it here
const visibilityWatcher = process.browser ? require('visibility')() : {};
const FORCE_TICK_INTERVAL = 1000;
let tickForced = false;
let callbacks = [];
const ReactRAFBatchingStrategy = {
// Every time React does a transaction, it checks this var.
// If it's false, it's going to go ahead and call `batchedUpdates`.
// Otherwise, it appends to `dirtyComponents` and waits for a flush.
//
// When this is false, we get default React behavior.
//
// When this is true, we'll start collecting components to render and wait for a flush.
// This is good to do when WS data comes down.
isBatchingUpdates: false,
/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
batchedUpdates(callback: Function, a: any, b: any, c: any, d: any, e: any) {
const wasBatching = this.isBatchingUpdates;
// If we were batching already, it's safe to call back on this. That will enqueue the update.
if (wasBatching) {
return callback(a, b, c, d, e);
}
// If we weren't batching, do what the default strategy does: say we're batching, enqueue the update,
// then flush it.
// Note that you *must* set `isBatchingUpdates` to true before calling back, otherwise the callback
// will call batchedUpdates again and you'll blow the stack.
// See ReactDefaultBatchingStrategy.js. It's complex to read b/c it uses a Transaction,
// but see the 'close' handlers of each transaction wrapper. Those are executed after the callback.
this.isBatchingUpdates = true;
callback(a, b, c, d, e); // eslint-disable-line callback-return
flushCallbacks();
ReactUpdates.flushBatchedUpdates();
this.isBatchingUpdates = false;
},
// Call this function on next tick.
onNextTick(fn: Function) {
callbacks.push(fn);
},
batch() {
// If we're not already batching, flush on the next rAF.
const wasBatching = this.isBatchingUpdates;
if (!wasBatching) {
this.isBatchingUpdates = true;
// If the page is hidden, rAF will never fire. This is generally good but we have things like native
// order fill notifications that users should see.
if (visibilityWatcher && visibilityWatcher.hidden()) {
tickForced = setTimeout(tick, FORCE_TICK_INTERVAL);
} else {
window.requestAnimationFrame(tick);
}
}
},
inject() {
// If the page becomes visible, flush updates right away if they were waiting for the force interval.
visibilityWatcher && visibilityWatcher.on('show', () => {
if (!tickForced) return;
clearTimeout(tickForced);
tick();
});
// If the page is hidden, start the forced tick interval. Otherwise it's possible
// for us to have been waiting on rAF and this just never goes.
visibilityWatcher && visibilityWatcher.on('hide', () => {
if (tickForced) return;
tickForced = setTimeout(tick, FORCE_TICK_INTERVAL);
});
ReactUpdates.injection.injectBatchingStrategy(this);
}
};
// Only flush if this has been explicitly enabled.
// This means we're going to enable it every time we get a WS message, but otherwise it doesn't tick.
function tick() {
tickForced = null;
flushCallbacks();
ReactUpdates.flushBatchedUpdates();
ReactRAFBatchingStrategy.isBatchingUpdates = false;
}
function flushCallbacks() {
for (let i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
callbacks = [];
}
export default ReactRAFBatchingStrategy;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment