diff --git a/scripts/fiber/tests-failing.txt b/scripts/fiber/tests-failing.txt index d816054f915..1da530c083b 100644 --- a/scripts/fiber/tests-failing.txt +++ b/scripts/fiber/tests-failing.txt @@ -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 diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 069436ff511..3cec34968c0 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -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 diff --git a/src/renderers/dom/ReactDOM.js b/src/renderers/dom/ReactDOM.js index ac5ce441689..5f46c11cbec 100644 --- a/src/renderers/dom/ReactDOM.js +++ b/src/renderers/dom/ReactDOM.js @@ -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(); @@ -37,7 +36,7 @@ var ReactDOM = { /* eslint-disable camelcase */ unstable_batchedUpdates: ReactUpdates.batchedUpdates, - unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer, + unstable_renderSubtreeIntoContainer: ReactMount.renderSubtreeIntoContainer, /* eslint-enable camelcase */ }; diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js index 91f25fe46a2..331646c1529 100644 --- a/src/renderers/dom/fiber/ReactDOMFiber.js +++ b/src/renderers/dom/fiber/ReactDOMFiber.js @@ -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, @@ -147,18 +154,29 @@ function warnAboutUnstableUse() { warned = true; } +function renderSubtreeIntoContainer(parentComponent : ?ReactComponent, element : ReactElement, 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, 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, element : ReactElement, 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) { diff --git a/src/renderers/dom/stack/client/ReactMount.js b/src/renderers/dom/stack/client/ReactMount.js index 36bd77bc822..b9c756ab179 100644 --- a/src/renderers/dom/stack/client/ReactMount.js +++ b/src/renderers/dom/stack/client/ReactMount.js @@ -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'); @@ -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) { diff --git a/src/renderers/dom/stack/client/renderSubtreeIntoContainer.js b/src/renderers/dom/stack/client/renderSubtreeIntoContainer.js index d47e2ad2e12..f69b3cc3e46 100644 --- a/src/renderers/dom/stack/client/renderSubtreeIntoContainer.js +++ b/src/renderers/dom/stack/client/renderSubtreeIntoContainer.js @@ -11,6 +11,6 @@ 'use strict'; -var ReactMount = require('ReactMount'); +var ReactDOM = require('ReactDOM'); -module.exports = ReactMount.renderSubtreeIntoContainer; +module.exports = ReactDOM.unstable_renderSubtreeIntoContainer; diff --git a/src/renderers/noop/ReactNoop.js b/src/renderers/noop/ReactNoop.js index f6c91e60a79..ebac23f6a24 100644 --- a/src/renderers/noop/ReactNoop.js +++ b/src/renderers/noop/ReactNoop.js @@ -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); } } }, diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 02fd265aa93..368bc19d065 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -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'; @@ -30,6 +31,7 @@ var { isContextProvider, hasContextChanged, pushContextProvider, + pushTopLevelContextObject, resetContext, } = require('ReactFiberContext'); var { @@ -411,11 +413,21 @@ module.exports = function( 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); diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 12c3fc29e3c..544245f11d4 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -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'; @@ -157,8 +158,14 @@ module.exports = function(config : HostConfig) { 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 @@ -166,6 +173,7 @@ module.exports = function(config : HostConfig) { // are invoked. markUpdate(workInProgress); return null; + } case HostComponent: let newProps = workInProgress.pendingProps; if (current && workInProgress.stateNode != null) { diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js index d2e09311abc..e18e35e2706 100644 --- a/src/renderers/shared/fiber/ReactFiberContext.js +++ b/src/renderers/shared/fiber/ReactFiberContext.js @@ -18,6 +18,7 @@ var emptyObject = require('emptyObject'); var invariant = require('invariant'); var { getComponentName, + isFiberMounted, } = require('ReactFiberTreeReflection'); var { ClassComponent, @@ -64,12 +65,13 @@ 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; @@ -77,10 +79,36 @@ exports.popContextProvider = function() : void { 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; @@ -88,21 +116,7 @@ exports.pushContextProvider = function(fiber : Fiber, didPerformWork : boolean) 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; } @@ -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; +}; diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js index 2f780f096bb..b1344c41213 100644 --- a/src/renderers/shared/fiber/ReactFiberReconciler.js +++ b/src/renderers/shared/fiber/ReactFiberReconciler.js @@ -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'); @@ -28,6 +32,8 @@ if (__DEV__) { var { findCurrentHostFiber } = require('ReactFiberTreeReflection'); +var getContextForSubtree = require('getContextForSubtree'); + export type Deadline = { timeRemaining : () => number }; @@ -64,8 +70,8 @@ export type HostConfig = { }; export type Reconciler = { - mountContainer(element : ReactElement, containerInfo : C) : OpaqueNode, - updateContainer(element : ReactElement, container : OpaqueNode) : void, + mountContainer(element : ReactElement, containerInfo : C, parentComponent : ?ReactComponent) : OpaqueNode, + updateContainer(element : ReactElement, container : OpaqueNode, parentComponent : ?ReactComponent) : void, unmountContainer(container : OpaqueNode) : void, performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void, /* eslint-disable no-undef */ @@ -81,6 +87,10 @@ export type Reconciler = { findHostInstance(component : Fiber) : I | TI | null, }; +getContextForSubtree._injectFiber(function(fiber : Fiber) { + return processChildContext(fiber, findCurrentUnmaskedContext(fiber)); +}); + module.exports = function(config : HostConfig) : Reconciler { var { @@ -92,8 +102,9 @@ module.exports = function(config : HostConfig) : return { - mountContainer(element : ReactElement, containerInfo : C, callback: ?Function) : OpaqueNode { - const root = createFiberRoot(containerInfo); + mountContainer(element : ReactElement, containerInfo : C, parentComponent : ?ReactComponent, callback: ?Function) : OpaqueNode { + const context = getContextForSubtree(parentComponent); + const root = createFiberRoot(containerInfo, context); const container = root.current; if (callback) { const queue = createUpdateQueue(null); @@ -117,7 +128,7 @@ module.exports = function(config : HostConfig) : return container; }, - updateContainer(element : ReactElement, container : OpaqueNode, callback: ?Function) : void { + updateContainer(element : ReactElement, container : OpaqueNode, parentComponent : ?ReactComponent, callback: ?Function) : void { // TODO: If this is a nested container, this won't be the root. const root : FiberRoot = (container.stateNode : any); if (callback) { @@ -127,6 +138,7 @@ module.exports = function(config : HostConfig) : addCallbackToQueue(queue, callback); root.callbackList = queue; } + root.pendingContext = getContextForSubtree(parentComponent); // TODO: Use pending work/state instead of props. root.current.pendingProps = element; diff --git a/src/renderers/shared/fiber/ReactFiberRoot.js b/src/renderers/shared/fiber/ReactFiberRoot.js index 93200d5d270..aeff58043b7 100644 --- a/src/renderers/shared/fiber/ReactFiberRoot.js +++ b/src/renderers/shared/fiber/ReactFiberRoot.js @@ -28,9 +28,12 @@ export type FiberRoot = { nextScheduledRoot: ?FiberRoot, // Linked list of callbacks to call after updates are committed. callbackList: ?UpdateQueue, + // Top context object, used by renderSubtreeIntoContainer + context: Object, + pendingContext: ?Object, }; -exports.createFiberRoot = function(containerInfo : any) : FiberRoot { +exports.createFiberRoot = function(containerInfo : any, context : Object) : FiberRoot { // Cyclic construction. This cheats the type system right now because // stateNode is any. const uninitializedFiber = createHostContainerFiber(); @@ -40,6 +43,8 @@ exports.createFiberRoot = function(containerInfo : any) : FiberRoot { isScheduled: false, nextScheduledRoot: null, callbackList: null, + context: context, + pendingContext: null, }; uninitializedFiber.stateNode = root; return root; diff --git a/src/renderers/shared/fiber/ReactFiberTreeReflection.js b/src/renderers/shared/fiber/ReactFiberTreeReflection.js index 0f66e094bb4..ff042c2720e 100644 --- a/src/renderers/shared/fiber/ReactFiberTreeReflection.js +++ b/src/renderers/shared/fiber/ReactFiberTreeReflection.js @@ -61,6 +61,7 @@ function isFiberMounted(fiber : Fiber) : number { // that has been unmounted. return UNMOUNTED; } +exports.isFiberMounted = isFiberMounted; exports.isMounted = function(component : ReactComponent) : boolean { var fiber : ?Fiber = ReactInstanceMap.get(component); diff --git a/src/renderers/shared/shared/getContextForSubtree.js b/src/renderers/shared/shared/getContextForSubtree.js new file mode 100644 index 00000000000..52e8d3611af --- /dev/null +++ b/src/renderers/shared/shared/getContextForSubtree.js @@ -0,0 +1,41 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule getContextForSubtree + * @flow + */ + +import type { Fiber } from 'ReactFiber'; + +const ReactInstanceMap = require('ReactInstanceMap'); + +const emptyObject = require('emptyObject'); +const invariant = require('invariant'); + +let getContextFiber = function(arg) { + invariant(false, 'Missing injection for fiber getContextForSubtree'); +}; + +function getContextForSubtree(parentComponent : ?ReactComponent) : Object { + if (!parentComponent) { + return emptyObject; + } + + const instance = ReactInstanceMap.get(parentComponent); + if (typeof instance.tag === 'number') { + return getContextFiber(instance); + } else { + return instance._processChildContext(instance._context); + } +} + +getContextForSubtree._injectFiber = function(fn) { + getContextFiber = fn; +}; + +module.exports = getContextForSubtree;