diff --git a/src/isomorphic/classic/element/ReactCurrentOwner.js b/src/isomorphic/classic/element/ReactCurrentOwner.js index da5786e875dc..e9a6ea5b5c7f 100644 --- a/src/isomorphic/classic/element/ReactCurrentOwner.js +++ b/src/isomorphic/classic/element/ReactCurrentOwner.js @@ -13,6 +13,7 @@ 'use strict'; import type { ReactInstance } from 'ReactInstanceType'; +import type { Fiber } from 'ReactFiber'; /** * Keeps track of the current owner. @@ -26,7 +27,7 @@ var ReactCurrentOwner = { * @internal * @type {ReactComponent} */ - current: (null: null | ReactInstance), + current: (null: null | ReactInstance | Fiber), }; diff --git a/src/isomorphic/hooks/ReactComponentTreeHook.js b/src/isomorphic/hooks/ReactComponentTreeHook.js index ae769001c959..cfae3b6401d4 100644 --- a/src/isomorphic/hooks/ReactComponentTreeHook.js +++ b/src/isomorphic/hooks/ReactComponentTreeHook.js @@ -315,9 +315,10 @@ var ReactComponentTreeHook = { } var currentOwner = ReactCurrentOwner.current; - var id = currentOwner && currentOwner._debugID; - - info += ReactComponentTreeHook.getStackAddendumByID(id); + if (currentOwner && typeof currentOwner._debugID === 'number') { + var id = currentOwner && currentOwner._debugID; + info += ReactComponentTreeHook.getStackAddendumByID(id); + } return info; }, diff --git a/src/renderers/native/findNodeHandle.js b/src/renderers/native/findNodeHandle.js index 0c30a344e04c..aca064b52954 100644 --- a/src/renderers/native/findNodeHandle.js +++ b/src/renderers/native/findNodeHandle.js @@ -18,6 +18,8 @@ var ReactInstanceMap = require('ReactInstanceMap'); var invariant = require('invariant'); var warning = require('warning'); +import type { ReactInstance } from 'ReactInstanceType'; + /** * ReactNative vs ReactWeb * ----------------------- @@ -50,7 +52,8 @@ var warning = require('warning'); function findNodeHandle(componentOrHandle: any): ?number { if (__DEV__) { - var owner = ReactCurrentOwner.current; + // TODO: fix this unsafe cast to work with Fiber. + var owner = ((ReactCurrentOwner.current: any): ReactInstance | null); if (owner !== null) { warning( owner._warnedAboutRefsInRender, @@ -61,6 +64,7 @@ function findNodeHandle(componentOrHandle: any): ?number { 'componentDidUpdate instead.', owner.getName() || 'A component' ); + owner._warnedAboutRefsInRender = true; } } diff --git a/src/renderers/shared/fiber/ReactChildFiber.js b/src/renderers/shared/fiber/ReactChildFiber.js index f51a00ba011e..4468c28aed4c 100644 --- a/src/renderers/shared/fiber/ReactChildFiber.js +++ b/src/renderers/shared/fiber/ReactChildFiber.js @@ -28,6 +28,7 @@ var ReactReifiedYield = require('ReactReifiedYield'); var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect'); var ReactTypeOfWork = require('ReactTypeOfWork'); +var emptyObject = require('emptyObject'); var getIteratorFn = require('getIteratorFn'); const { @@ -47,6 +48,7 @@ const { const isArray = Array.isArray; const { + ClassComponent, HostText, CoroutineComponent, YieldComponent, @@ -62,6 +64,31 @@ const { Deletion, } = ReactTypeOfSideEffect; +function transferRef(current: ?Fiber, workInProgress: Fiber, element: ReactElement) { + if (typeof element.ref === 'string') { + if (element._owner) { + const ownerFiber : ?Fiber = (element._owner : any); + if (ownerFiber && ownerFiber.tag === ClassComponent) { + const stringRef = element.ref; + // Check if previous string ref matches new string ref + if (current && current.ref && current.ref._stringRef === stringRef) { + workInProgress.ref = current.ref; + return; + } + const inst = ownerFiber.stateNode; + const ref = function(value) { + const refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs; + refs[stringRef] = value; + }; + ref._stringRef = stringRef; + workInProgress.ref = ref; + } + } + } else { + workInProgress.ref = element.ref; + } +} + // This wrapper function exists because I expect to clone the code in each path // to be able to optimize each path individually by branching early. This needs // a compiler or we can do it manually. Helpers that don't need this branching @@ -221,13 +248,13 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) { if (current == null || current.type !== element.type) { // Insert const created = createFiberFromElement(element, priority); - created.ref = element.ref; + transferRef(current, created, element); created.return = returnFiber; return created; } else { // Move based on index const existing = useFiber(current, priority); - existing.ref = element.ref; + transferRef(current, existing, element); existing.pendingProps = element.props; existing.return = returnFiber; return existing; @@ -319,7 +346,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { const created = createFiberFromElement(newChild, priority); - created.ref = newChild.ref; + transferRef(null, created, newChild); created.return = returnFiber; return created; } @@ -653,7 +680,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) { if (child.type === element.type) { deleteRemainingChildren(returnFiber, child.sibling); const existing = useFiber(child, priority); - existing.ref = element.ref; + transferRef(child, existing, element); existing.pendingProps = element.props; existing.return = returnFiber; return existing; @@ -668,7 +695,7 @@ function ChildReconciler(shouldClone, shouldTrackSideEffects) { } const created = createFiberFromElement(element, priority); - created.ref = element.ref; + transferRef(currentFirstChild, created, element); created.return = returnFiber; return created; } diff --git a/src/renderers/shared/fiber/ReactFiber.js b/src/renderers/shared/fiber/ReactFiber.js index 2c6f47f17ae4..739f6636607f 100644 --- a/src/renderers/shared/fiber/ReactFiber.js +++ b/src/renderers/shared/fiber/ReactFiber.js @@ -39,11 +39,18 @@ var { NoEffect, } = require('ReactTypeOfSideEffect'); -// An Instance is shared between all versions of a component. We can easily -// break this out into a separate object to avoid copying so much to the -// alternate versions of the tree. We put this on a single object for now to -// minimize the number of objects created during the initial render. -type Instance = { +// A Fiber is work on a Component that needs to be done or was done. There can +// be more than one per component. +export type Fiber = { + // These first fields are conceptually members of an Instance. This used to + // be split into a separate type and intersected with the other Fiber fields, + // but until Flow fixes its intersection bugs, we've merged them into a + // single type. + + // An Instance is shared between all versions of a component. We can easily + // break this out into a separate object to avoid copying so much to the + // alternate versions of the tree. We put this on a single object for now to + // minimize the number of objects created during the initial render. // Tag identifying the type of fiber. tag: TypeOfWork, @@ -61,11 +68,7 @@ type Instance = { // parent : Instance -> return The parent happens to be the same as the // return fiber since we've merged the fiber and instance. -}; - -// A Fiber is work on a Component that needs to be done or was done. There can -// be more than one per component. -export type Fiber = Instance & { + // Remaining fields belong to Fiber // The Fiber to return to after finishing processing this one. // This is effectively the parent, but there can be multiple parents (two) @@ -80,7 +83,7 @@ export type Fiber = Instance & { // The ref last used to attach this node. // I'll avoid adding an owner field for prod and model that as functions. - ref: null | (handle : ?Object) => void, + ref: null | (((handle : ?Object) => void) & { _stringRef: ?string }), // Input is the data coming into process this fiber. Arguments. Props. pendingProps: any, // This type will be more specific once we overload the tag. @@ -327,4 +330,3 @@ exports.createFiberFromYield = function(yieldNode : ReactYield, priorityLevel : fiber.pendingProps = {}; return fiber; }; - diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index c199c8c921f7..7112101d3083 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -16,6 +16,7 @@ import type { ReactCoroutine } from 'ReactCoroutine'; import type { Fiber } from 'ReactFiber'; import type { HostConfig } from 'ReactFiberReconciler'; import type { PriorityLevel } from 'ReactPriorityLevel'; +import ReactCurrentOwner from 'ReactCurrentOwner'; var { mountChildFibersInPlace, @@ -151,7 +152,12 @@ module.exports = function(config : HostConfig, s } } - var nextChildren = fn(props); + if (__DEV__) { + ReactCurrentOwner.current = workInProgress; + var nextChildren = fn(props); + } else { + var nextChildren = fn(props); + } reconcileChildren(current, workInProgress, nextChildren); return workInProgress.child; } @@ -176,6 +182,7 @@ module.exports = function(config : HostConfig, s } // Rerender const instance = workInProgress.stateNode; + ReactCurrentOwner.current = workInProgress; const nextChildren = instance.render(); reconcileChildren(current, workInProgress, nextChildren); return workInProgress.child; @@ -237,12 +244,20 @@ module.exports = function(config : HostConfig, s } var fn = workInProgress.type; var props = workInProgress.pendingProps; - var value = fn(props); + + if (__DEV__) { + ReactCurrentOwner.current = workInProgress; + var value = fn(props); + } else { + var value = fn(props); + } + if (typeof value === 'object' && value && typeof value.render === 'function') { // Proceed under the assumption that this is a class instance workInProgress.tag = ClassComponent; adoptClassInstance(workInProgress, value); mountClassInstance(workInProgress); + ReactCurrentOwner.current = workInProgress; value = value.render(); } else { // Proceed under the assumption that this is a functional component diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index e7acda55c523..b50a3f024d4b 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -20,6 +20,7 @@ import type { PriorityLevel } from 'ReactPriorityLevel'; var ReactFiberBeginWork = require('ReactFiberBeginWork'); var ReactFiberCompleteWork = require('ReactFiberCompleteWork'); var ReactFiberCommitWork = require('ReactFiberCommitWork'); +var ReactCurrentOwner = require('ReactCurrentOwner'); var { cloneFiber } = require('ReactFiber'); @@ -298,6 +299,8 @@ module.exports = function(config : HostConfig) { } } + ReactCurrentOwner.current = null; + return next; } diff --git a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js index ab8f7a55d7cb..5116d0c5ec03 100644 --- a/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js +++ b/src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js @@ -1072,4 +1072,28 @@ describe('ReactIncrementalSideEffects', () => { // TODO: Test that mounts, updates, refs, unmounts and deletions happen in the // expected way for aborted and resumed render life-cycles. + it('supports string refs', () => { + var fooInstance = null; + + class Bar extends React.Component { + componentDidMount() { + this.test = 'test'; + } + render() { + return
; + } + } + + class Foo extends React.Component { + render() { + fooInstance = this; + return ; + } + } + + ReactNoop.render(); + ReactNoop.flush(); + + expect(fooInstance.refs.bar.test).toEqual('test'); + }); });