diff --git a/src/renderers/__tests__/ReactStatelessComponent-test.js b/src/renderers/__tests__/ReactStatelessComponent-test.js index c8d67c7bbfc2c..577a6c1ee13d6 100644 --- a/src/renderers/__tests__/ReactStatelessComponent-test.js +++ b/src/renderers/__tests__/ReactStatelessComponent-test.js @@ -295,6 +295,43 @@ describe('ReactStatelessComponent', () => { console.error.calls.reset(); }); + // This guards against a regression caused by clearing the current debug fiber. + // https://github.com/facebook/react/issues/10831 + it('should warn when giving a function ref with context', () => { + spyOn(console, 'error'); + + function Child() { + return null; + } + Child.contextTypes = { + foo: PropTypes.string, + }; + + class Parent extends React.Component { + static childContextTypes = { + foo: PropTypes.string, + }; + getChildContext() { + return { + foo: 'bar', + }; + } + render() { + return ; + } + } + + ReactTestUtils.renderIntoDocument(); + expectDev(console.error.calls.count()).toBe(1); + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Stateless function components cannot be given refs. ' + + 'Attempts to access this ref will fail.\n\nCheck the render method ' + + 'of `Parent`.\n' + + ' in Child (at **)\n' + + ' in Parent (at **)', + ); + }); + it('should provide a null ref', () => { function Child() { return
; diff --git a/src/renderers/shared/fiber/ReactDebugCurrentFiber.js b/src/renderers/shared/fiber/ReactDebugCurrentFiber.js index a34e1d14e5e2b..16a4096bd7282 100644 --- a/src/renderers/shared/fiber/ReactDebugCurrentFiber.js +++ b/src/renderers/shared/fiber/ReactDebugCurrentFiber.js @@ -55,9 +55,13 @@ function resetCurrentFiber() { ReactDebugCurrentFiber.phase = null; } -function setCurrentFiber(fiber: Fiber | null, phase: LifeCyclePhase | null) { +function setCurrentFiber(fiber: Fiber) { ReactDebugCurrentFrame.getCurrentStack = getCurrentFiberStackAddendum; ReactDebugCurrentFiber.current = fiber; + ReactDebugCurrentFiber.phase = null; +} + +function setCurrentPhase(phase: LifeCyclePhase | null) { ReactDebugCurrentFiber.phase = phase; } @@ -66,6 +70,7 @@ var ReactDebugCurrentFiber = { phase: (null: LifeCyclePhase | null), resetCurrentFiber, setCurrentFiber, + setCurrentPhase, getCurrentFiberOwnerName, getCurrentFiberStackAddendum, }; diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 9b945305d4b05..ed81a1d778944 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -207,9 +207,9 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; - ReactDebugCurrentFiber.setCurrentFiber(workInProgress, 'render'); + ReactDebugCurrentFiber.setCurrentPhase('render'); nextChildren = fn(nextProps, context); - ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); + ReactDebugCurrentFiber.setCurrentPhase(null); } else { nextChildren = fn(nextProps, context); } @@ -281,9 +281,9 @@ module.exports = function( ReactCurrentOwner.current = workInProgress; let nextChildren; if (__DEV__) { - ReactDebugCurrentFiber.setCurrentFiber(workInProgress, 'render'); + ReactDebugCurrentFiber.setCurrentPhase('render'); nextChildren = instance.render(); - ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); + ReactDebugCurrentFiber.setCurrentPhase(null); } else { nextChildren = instance.render(); } @@ -725,10 +725,6 @@ module.exports = function( return bailoutOnLowPriority(current, workInProgress); } - if (__DEV__) { - ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); - } - switch (workInProgress.tag) { case IndeterminateComponent: return mountIndeterminateComponent( diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 41393a19e22ef..451e379965da1 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -42,10 +42,6 @@ var { var {Placement, Ref, Update} = ReactTypeOfSideEffect; var {OffscreenPriority} = ReactPriorityLevel; -if (__DEV__) { - var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); -} - var invariant = require('fbjs/lib/invariant'); module.exports = function( @@ -187,10 +183,6 @@ module.exports = function( workInProgress: Fiber, renderPriority: PriorityLevel, ): Fiber | null { - if (__DEV__) { - ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); - } - // Get the latest props. let newProps = workInProgress.pendingProps; if (newProps === null) { diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js index fb0d4dda3ade9..8c5bb96526177 100644 --- a/src/renderers/shared/fiber/ReactFiberContext.js +++ b/src/renderers/shared/fiber/ReactFiberContext.js @@ -89,7 +89,6 @@ exports.getMaskedContext = function( if (__DEV__) { const name = getComponentName(workInProgress) || 'Unknown'; - ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); checkPropTypes( contextTypes, context, @@ -97,7 +96,6 @@ exports.getMaskedContext = function( name, ReactDebugCurrentFiber.getCurrentFiberStackAddendum, ); - ReactDebugCurrentFiber.resetCurrentFiber(); } // Cache unmasked context so we can avoid recreating masked context unless necessary. @@ -153,11 +151,7 @@ exports.pushTopLevelContextObject = function( push(didPerformWorkStackCursor, didChange, fiber); }; -function processChildContext( - fiber: Fiber, - parentContext: Object, - isReconciling: boolean, -): Object { +function processChildContext(fiber: Fiber, parentContext: Object): Object { const instance = fiber.stateNode; const childContextTypes = fiber.type.childContextTypes; @@ -184,11 +178,11 @@ function processChildContext( let childContext; if (__DEV__) { - ReactDebugCurrentFiber.setCurrentFiber(fiber, 'getChildContext'); + ReactDebugCurrentFiber.setCurrentPhase('getChildContext'); startPhaseTimer(fiber, 'getChildContext'); childContext = instance.getChildContext(); stopPhaseTimer(); - ReactDebugCurrentFiber.resetCurrentFiber(); + ReactDebugCurrentFiber.setCurrentPhase(null); } else { childContext = instance.getChildContext(); } @@ -202,21 +196,18 @@ function processChildContext( } if (__DEV__) { const name = getComponentName(fiber) || 'Unknown'; - // We can only provide accurate element stacks if we pass work-in-progress tree - // during the begin or complete phase. However currently this function is also - // called from unstable_renderSubtree legacy implementation. In this case it unsafe to - // assume anything about the given fiber. We won't pass it down if we aren't sure. - // TODO: remove this hack when we delete unstable_renderSubtree in Fiber. - const workInProgress = isReconciling ? fiber : null; - ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); checkPropTypes( childContextTypes, childContext, 'child context', name, + // In practice, there is one case in which we won't get a stack. It's when + // somebody calls unstable_renderSubtreeIntoContainer() and we process + // context from the parent component instance. The stack will be missing + // because it's outside of the reconciliation, and so the pointer has not + // been set. This is rare and doesn't matter. We'll also remove that API. ReactDebugCurrentFiber.getCurrentFiberStackAddendum, ); - ReactDebugCurrentFiber.resetCurrentFiber(); } return {...parentContext, ...childContext}; @@ -264,11 +255,7 @@ exports.invalidateContextProvider = function( // Merge parent and own context. // Skip this if we're not updating due to sCU. // This avoids unnecessarily recomputing memoized values. - const mergedContext = processChildContext( - workInProgress, - previousContext, - true, - ); + const mergedContext = processChildContext(workInProgress, previousContext); instance.__reactInternalMemoizedMergedChildContext = mergedContext; // Replace the old (or empty) context with the new one. diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js index 8b9fa1d3df7e0..23738f3c40143 100644 --- a/src/renderers/shared/fiber/ReactFiberReconciler.js +++ b/src/renderers/shared/fiber/ReactFiberReconciler.js @@ -181,7 +181,7 @@ export type Reconciler = { getContextForSubtree._injectFiber(function(fiber: Fiber) { const parentContext = findCurrentUnmaskedContext(fiber); return isContextProvider(fiber) - ? processChildContext(fiber, parentContext, false) + ? processChildContext(fiber, parentContext) : parentContext; }); diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index dcaea4aa86092..495bb550de265 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -317,7 +317,7 @@ module.exports = function( function commitAllHostEffects() { while (nextEffect !== null) { if (__DEV__) { - ReactDebugCurrentFiber.setCurrentFiber(nextEffect, null); + ReactDebugCurrentFiber.setCurrentFiber(nextEffect); recordEffect(); } @@ -615,7 +615,13 @@ module.exports = function( // means that we don't need an additional field on the work in // progress. const current = workInProgress.alternate; + if (__DEV__) { + ReactDebugCurrentFiber.setCurrentFiber(workInProgress); + } const next = completeWork(current, workInProgress, nextPriorityLevel); + if (__DEV__) { + ReactDebugCurrentFiber.resetCurrentFiber(); + } const returnFiber = workInProgress.return; const siblingFiber = workInProgress.sibling; @@ -706,8 +712,12 @@ module.exports = function( // See if beginning this work spawns more work. if (__DEV__) { startWorkTimer(workInProgress); + ReactDebugCurrentFiber.setCurrentFiber(workInProgress); } let next = beginWork(current, workInProgress, nextPriorityLevel); + if (__DEV__) { + ReactDebugCurrentFiber.resetCurrentFiber(); + } if (__DEV__ && ReactFiberInstrumentation.debugTool) { ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress); } @@ -718,9 +728,6 @@ module.exports = function( } ReactCurrentOwner.current = null; - if (__DEV__) { - ReactDebugCurrentFiber.resetCurrentFiber(); - } return next; } @@ -735,8 +742,12 @@ module.exports = function( // See if beginning this work spawns more work. if (__DEV__) { startWorkTimer(workInProgress); + ReactDebugCurrentFiber.setCurrentFiber(workInProgress); } let next = beginFailedWork(current, workInProgress, nextPriorityLevel); + if (__DEV__) { + ReactDebugCurrentFiber.resetCurrentFiber(); + } if (__DEV__ && ReactFiberInstrumentation.debugTool) { ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress); } @@ -747,9 +758,6 @@ module.exports = function( } ReactCurrentOwner.current = null; - if (__DEV__) { - ReactDebugCurrentFiber.resetCurrentFiber(); - } return next; }