Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions scripts/fiber/tests-failing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ src/addons/__tests__/ReactFragment-test.js
* should throw if a plain object even if it is in an owner
* should throw if a plain object looks like an old element

src/addons/__tests__/renderSubtreeIntoContainer-test.js
* should pass context when rendering subtree elsewhere
* should update context if it changes due to setState
* should update context if it changes due to re-render

src/isomorphic/classic/__tests__/ReactContextValidator-test.js
* should pass previous context to lifecycles

Expand Down
3 changes: 3 additions & 0 deletions scripts/fiber/tests-passing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ src/addons/__tests__/ReactFragment-test.js
* should warn if passing a ReactElement to createFragment

src/addons/__tests__/renderSubtreeIntoContainer-test.js
* should pass context when rendering subtree elsewhere
* should throw if parentComponent is invalid
* should update context if it changes due to setState
* should update context if it changes due to re-render

src/addons/__tests__/update-test.js
* pushes
Expand Down
3 changes: 1 addition & 2 deletions src/renderers/dom/ReactDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ var ReactVersion = require('ReactVersion');

var findDOMNode = require('findDOMNode');
var getHostComponentFromComposite = require('getHostComponentFromComposite');
var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer');
var warning = require('warning');

ReactDOMInjection.inject();
Expand All @@ -37,7 +36,7 @@ var ReactDOM = {

/* eslint-disable camelcase */
unstable_batchedUpdates: ReactUpdates.batchedUpdates,
unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer,
unstable_renderSubtreeIntoContainer: ReactMount.renderSubtreeIntoContainer,
/* eslint-enable camelcase */
};

Expand Down
34 changes: 26 additions & 8 deletions src/renderers/dom/fiber/ReactDOMFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,22 @@ import type { Fiber } from 'ReactFiber';
import type { HostChildren } from 'ReactFiberReconciler';

var ReactControlledComponent = require('ReactControlledComponent');
var ReactFiberReconciler = require('ReactFiberReconciler');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
var ReactDOMFiberComponent = require('ReactDOMFiberComponent');
var ReactDOMInjection = require('ReactDOMInjection');
var ReactFiberReconciler = require('ReactFiberReconciler');
var ReactInstanceMap = require('ReactInstanceMap');

var findDOMNode = require('findDOMNode');
var invariant = require('invariant');
var warning = require('warning');

ReactDOMInjection.inject();
ReactControlledComponent.injection.injectFiberControlledHostComponent(
ReactDOMFiberComponent
);

var {
createElement,
setInitialProperties,
Expand Down Expand Up @@ -147,18 +154,29 @@ function warnAboutUnstableUse() {
warned = true;
}

function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
let root;
if (!container._reactRootContainer) {
root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
} else {
DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);
}
return DOMRenderer.getPublicRootInstance(root);
}

var ReactDOM = {

render(element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
warnAboutUnstableUse();
let root;
return renderSubtreeIntoContainer(null, element, container, callback);
},

if (!container._reactRootContainer) {
root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, callback);
} else {
DOMRenderer.updateContainer(element, root = container._reactRootContainer, callback);
}
return DOMRenderer.getPublicRootInstance(root);
unstable_renderSubtreeIntoContainer(parentComponent : ReactComponent<any, any, any>, element : ReactElement<any>, container : DOMContainerElement, callback: ?Function) {
invariant(
parentComponent != null && ReactInstanceMap.has(parentComponent),
'parentComponent must be a valid React Component'
);
return renderSubtreeIntoContainer(parentComponent, element, container, callback);
},

unmountComponentAtNode(container : DOMContainerElement) {
Expand Down
11 changes: 2 additions & 9 deletions src/renderers/dom/stack/client/ReactMount.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ var ReactReconciler = require('ReactReconciler');
var ReactUpdateQueue = require('ReactUpdateQueue');
var ReactUpdates = require('ReactUpdates');

var emptyObject = require('emptyObject');
var getContextForSubtree = require('getContextForSubtree');
var instantiateReactComponent = require('instantiateReactComponent');
var invariant = require('invariant');
var setInnerHTML = require('setInnerHTML');
Expand Down Expand Up @@ -466,14 +466,7 @@ var ReactMount = {
{ child: nextElement }
);

var nextContext;
if (parentComponent) {
var parentInst = ReactInstanceMap.get(parentComponent);
nextContext = parentInst._processChildContext(parentInst._context);
} else {
nextContext = emptyObject;
}

var nextContext = getContextForSubtree(parentComponent);
var prevComponent = getTopLevelWrapperInContainer(container);

if (prevComponent) {
Expand Down
4 changes: 2 additions & 2 deletions src/renderers/dom/stack/client/renderSubtreeIntoContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@

'use strict';

var ReactMount = require('ReactMount');
var ReactDOM = require('ReactDOM');

module.exports = ReactMount.renderSubtreeIntoContainer;
module.exports = ReactDOM.unstable_renderSubtreeIntoContainer;
4 changes: 2 additions & 2 deletions src/renderers/noop/ReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,12 @@ var ReactNoop = {
if (!roots.has(rootID)) {
const container = { rootID: rootID, children: [] };
rootContainers.set(rootID, container);
const root = NoopRenderer.mountContainer(element, container, callback);
const root = NoopRenderer.mountContainer(element, container, null, callback);
roots.set(rootID, root);
} else {
const root = roots.get(rootID);
if (root) {
NoopRenderer.updateContainer(element, root, callback);
NoopRenderer.updateContainer(element, root, null, callback);
}
}
},
Expand Down
14 changes: 13 additions & 1 deletion src/renderers/shared/fiber/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import type { ReactCoroutine } from 'ReactCoroutine';
import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
import type { PriorityLevel } from 'ReactPriorityLevel';

Expand All @@ -30,6 +31,7 @@ var {
isContextProvider,
hasContextChanged,
pushContextProvider,
pushTopLevelContextObject,
resetContext,
} = require('ReactFiberContext');
var {
Expand Down Expand Up @@ -411,11 +413,21 @@ module.exports = function<T, P, I, TI, C>(
return updateFunctionalComponent(current, workInProgress);
case ClassComponent:
return updateClassComponent(current, workInProgress);
case HostContainer:
case HostContainer: {
const root = (workInProgress.stateNode : FiberRoot);
if (root.pendingContext) {
pushTopLevelContextObject(
root.pendingContext,
root.pendingContext !== root.context
);
} else {
pushTopLevelContextObject(root.context, false);
}
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
// A yield component is just a placeholder, we can just run through the
// next one immediately.
return workInProgress.child;
}
case HostComponent:
if (workInProgress.stateNode && typeof config.beginUpdate === 'function') {
config.beginUpdate(workInProgress.stateNode);
Expand Down
10 changes: 9 additions & 1 deletion src/renderers/shared/fiber/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import type { ReactCoroutine } from 'ReactCoroutine';
import type { Fiber } from 'ReactFiber';
import type { FiberRoot } from 'ReactFiberRoot';
import type { HostConfig } from 'ReactFiberReconciler';
import type { ReifiedYield } from 'ReactReifiedYield';

Expand Down Expand Up @@ -157,15 +158,22 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
markCallback(workInProgress);
}
return null;
case HostContainer:
case HostContainer: {
transferOutput(workInProgress.child, workInProgress);
popContextProvider();
const fiberRoot = (workInProgress.stateNode : FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
// We don't know if a container has updated any children so we always
// need to update it right now. We schedule this side-effect before
// all the other side-effects in the subtree. We need to schedule it
// before so that the entire tree is up-to-date before the life-cycles
// are invoked.
markUpdate(workInProgress);
return null;
}
case HostComponent:
let newProps = workInProgress.pendingProps;
if (current && workInProgress.stateNode != null) {
Expand Down
67 changes: 49 additions & 18 deletions src/renderers/shared/fiber/ReactFiberContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var emptyObject = require('emptyObject');
var invariant = require('invariant');
var {
getComponentName,
isFiberMounted,
} = require('ReactFiberTreeReflection');
var {
ClassComponent,
Expand Down Expand Up @@ -64,45 +65,58 @@ exports.hasContextChanged = function() : boolean {
return index > -1 && didPerformWorkStack[index];
};

exports.isContextProvider = function(fiber : Fiber) : boolean {
function isContextProvider(fiber : Fiber) : boolean {
return (
fiber.tag === ClassComponent &&
typeof fiber.stateNode.getChildContext === 'function'
);
};
}
exports.isContextProvider = isContextProvider;

exports.popContextProvider = function() : void {
contextStack[index] = emptyObject;
didPerformWorkStack[index] = false;
index--;
};

exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) : void {
exports.pushTopLevelContextObject = function(context : Object, didChange : boolean) : void {
invariant(index === -1, 'Unexpected context found on stack');
index++;
contextStack[index] = context;
didPerformWorkStack[index] = didChange;
};

function processChildContext(fiber : Fiber, parentContext : Object): Object {
const instance = fiber.stateNode;
const childContextTypes = fiber.type.childContextTypes;
const childContext = instance.getChildContext();
for (let contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentName(fiber),
contextKey
);
}
if (__DEV__) {
const name = getComponentName(fiber);
const debugID = 0; // TODO: pass a real ID
checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
}
return {...parentContext, ...childContext};
}
exports.processChildContext = processChildContext;

exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) : void {
const instance = fiber.stateNode;
const memoizedMergedChildContext = instance.__reactInternalMemoizedMergedChildContext;
const canReuseMergedChildContext = !didPerformWork && memoizedMergedChildContext != null;

let mergedContext = null;
if (canReuseMergedChildContext) {
mergedContext = memoizedMergedChildContext;
} else {
const childContext = instance.getChildContext();
for (let contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentName(fiber),
contextKey
);
}
if (__DEV__) {
const name = getComponentName(fiber);
const debugID = 0; // TODO: pass a real ID
checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, debugID);
}
mergedContext = {...getUnmaskedContext(), ...childContext};
mergedContext = processChildContext(fiber, getUnmaskedContext());
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
}

Expand All @@ -115,3 +129,20 @@ exports.resetContext = function() : void {
index = -1;
};

exports.findCurrentUnmaskedContext = function(fiber: Fiber) : Object {
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
// makes sense elsewhere
invariant(
isFiberMounted(fiber) && fiber.tag === ClassComponent,
'Expected subtree parent to be a mounted class component'
);

let node : ?Fiber = parent;
while (node) {
if (isContextProvider(fiber)) {
return fiber.stateNode.__reactInternalMemoizedMergedChildContext;
}
node = node.return;
}
return emptyObject;
};
22 changes: 17 additions & 5 deletions src/renderers/shared/fiber/ReactFiberReconciler.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import type { FiberRoot } from 'ReactFiberRoot';
import type { TypeOfWork } from 'ReactTypeOfWork';
import type { PriorityLevel } from 'ReactPriorityLevel';

var {
findCurrentUnmaskedContext,
processChildContext,
} = require('ReactFiberContext');
var { createFiberRoot } = require('ReactFiberRoot');
var ReactFiberScheduler = require('ReactFiberScheduler');

Expand All @@ -28,6 +32,8 @@ if (__DEV__) {

var { findCurrentHostFiber } = require('ReactFiberTreeReflection');

var getContextForSubtree = require('getContextForSubtree');

export type Deadline = {
timeRemaining : () => number
};
Expand Down Expand Up @@ -64,8 +70,8 @@ export type HostConfig<T, P, I, TI, C> = {
};

export type Reconciler<C, I, TI> = {
mountContainer(element : ReactElement<any>, containerInfo : C) : OpaqueNode,
updateContainer(element : ReactElement<any>, container : OpaqueNode) : void,
mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
unmountContainer(container : OpaqueNode) : void,
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
/* eslint-disable no-undef */
Expand All @@ -81,6 +87,10 @@ export type Reconciler<C, I, TI> = {
findHostInstance(component : Fiber) : I | TI | null,
};

getContextForSubtree._injectFiber(function(fiber : Fiber) {
return processChildContext(fiber, findCurrentUnmaskedContext(fiber));
});

module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) : Reconciler<C, I, TI> {

var {
Expand All @@ -92,8 +102,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :

return {

mountContainer(element : ReactElement<any>, containerInfo : C, callback: ?Function) : OpaqueNode {
const root = createFiberRoot(containerInfo);
mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
const context = getContextForSubtree(parentComponent);
const root = createFiberRoot(containerInfo, context);
const container = root.current;
if (callback) {
const queue = createUpdateQueue(null);
Expand All @@ -117,7 +128,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
return container;
},

updateContainer(element : ReactElement<any>, container : OpaqueNode, callback: ?Function) : void {
updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
// TODO: If this is a nested container, this won't be the root.
const root : FiberRoot = (container.stateNode : any);
if (callback) {
Expand All @@ -127,6 +138,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
addCallbackToQueue(queue, callback);
root.callbackList = queue;
}
root.pendingContext = getContextForSubtree(parentComponent);
// TODO: Use pending work/state instead of props.
root.current.pendingProps = element;

Expand Down
Loading