Files
react/packages/events/ReactControlledComponent.js
Andrew Clark 8a09a2fc53 Interactive updates (#12100)
* Updates inside controlled events (onChange) are sync even in async mode

This guarantees the DOM is in a consistent state before we yield back
to the browser.

We'll need to figure out a separate strategy for other
interactive events.

* Don't rely on flushing behavior of public batchedUpdates implementation

Flush work as an explicit step at the end of the event, right before
restoring controlled state.

* Interactive updates

At the beginning of an interactive browser event (events that fire as
the result of a user interaction, like a click), check for pending
updates that were scheduled in a previous interactive event. Flush the
pending updates synchronously so that the event handlers are up-to-date
before responding to the current event.

We now have three classes of events:

- Controlled events. Updates are always flushed synchronously.
- Interactive events. Updates are async, unless another a subsequent
event is fired before it can complete, as described above. They are
also slightly higher priority than a normal async update.
- Non-interactive events. These are treated as normal, low-priority
async updates.

* Flush lowest pending interactive update time

Accounts for case when multiple interactive updates are scheduled at
different priorities. This can happen when an interactive event is
dispatched inside an async subtree, and there's an event handler on
an ancestor that is outside the subtree.

* Update comment about restoring controlled components
2018-01-29 23:49:10 -08:00

86 lines
2.2 KiB
JavaScript

/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import invariant from 'fbjs/lib/invariant';
import {
getInstanceFromNode,
getFiberCurrentPropsFromNode,
} from './EventPluginUtils';
// Use to restore controlled state after a change event has fired.
let fiberHostComponent = null;
const ReactControlledComponentInjection = {
injectFiberControlledHostComponent: function(hostComponentImpl) {
// The fiber implementation doesn't use dynamic dispatch so we need to
// inject the implementation.
fiberHostComponent = hostComponentImpl;
},
};
let restoreTarget = null;
let restoreQueue = null;
function restoreStateOfTarget(target) {
// We perform this translation at the end of the event loop so that we
// always receive the correct fiber here
const internalInstance = getInstanceFromNode(target);
if (!internalInstance) {
// Unmounted
return;
}
invariant(
fiberHostComponent &&
typeof fiberHostComponent.restoreControlledState === 'function',
'Fiber needs to be injected to handle a fiber target for controlled ' +
'events. This error is likely caused by a bug in React. Please file an issue.',
);
const props = getFiberCurrentPropsFromNode(internalInstance.stateNode);
fiberHostComponent.restoreControlledState(
internalInstance.stateNode,
internalInstance.type,
props,
);
}
export const injection = ReactControlledComponentInjection;
export function enqueueStateRestore(target) {
if (restoreTarget) {
if (restoreQueue) {
restoreQueue.push(target);
} else {
restoreQueue = [target];
}
} else {
restoreTarget = target;
}
}
export function needsStateRestore(): boolean {
return restoreTarget !== null || restoreQueue !== null;
}
export function restoreStateIfNeeded() {
if (!restoreTarget) {
return;
}
const target = restoreTarget;
const queuedTargets = restoreQueue;
restoreTarget = null;
restoreQueue = null;
restoreStateOfTarget(target);
if (queuedTargets) {
for (let i = 0; i < queuedTargets.length; i++) {
restoreStateOfTarget(queuedTargets[i]);
}
}
}