Skip to content

Commit 83a536e

Browse files
authored
Fix "null" instead of the component stack in a warning (#10915)
* Add a failing test that prints "null" in the warning instead of the stack * Don't reset the current fiber during reconciliation It should only be reset when we stop doing work. Otherwise, any warnings after the reset will lose the component stack. The reset in getMaskedContext() was completely unnecessary. It is always called with the same fiber as the current work in progress. Therefore, I add a DEV-only warning assertion to ensure we don't regress, and remove the reset. The reset in processChildContext() is necessary because it can be called outside of reconciliation. Unfortunately, we have to keep this hack in until we can remove unstable_renderSubtreeIntoContainer(). To work around it, I restore the previous fiber instead of resetting. * Decouple setting the current fiber and phase These are two distinct actions. This helps make it clearer when we're actually changing the current pointer. I'm also removing an overengineered hack I previously added for unstable_renderSubtreeIntoContainer. It's not necessary now that we don't null the pointer all the time. This makes the code more straightforward. * Centralize the pointer updates in the scheduler
1 parent 47a6ac5 commit 83a536e

File tree

7 files changed

+72
-47
lines changed

7 files changed

+72
-47
lines changed

src/renderers/__tests__/ReactStatelessComponent-test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,43 @@ describe('ReactStatelessComponent', () => {
295295
console.error.calls.reset();
296296
});
297297

298+
// This guards against a regression caused by clearing the current debug fiber.
299+
// https://github.com/facebook/react/issues/10831
300+
it('should warn when giving a function ref with context', () => {
301+
spyOn(console, 'error');
302+
303+
function Child() {
304+
return null;
305+
}
306+
Child.contextTypes = {
307+
foo: PropTypes.string,
308+
};
309+
310+
class Parent extends React.Component {
311+
static childContextTypes = {
312+
foo: PropTypes.string,
313+
};
314+
getChildContext() {
315+
return {
316+
foo: 'bar',
317+
};
318+
}
319+
render() {
320+
return <Child ref={function() {}} />;
321+
}
322+
}
323+
324+
ReactTestUtils.renderIntoDocument(<Parent />);
325+
expectDev(console.error.calls.count()).toBe(1);
326+
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
327+
'Warning: Stateless function components cannot be given refs. ' +
328+
'Attempts to access this ref will fail.\n\nCheck the render method ' +
329+
'of `Parent`.\n' +
330+
' in Child (at **)\n' +
331+
' in Parent (at **)',
332+
);
333+
});
334+
298335
it('should provide a null ref', () => {
299336
function Child() {
300337
return <div />;

src/renderers/shared/fiber/ReactDebugCurrentFiber.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ function resetCurrentFiber() {
5555
ReactDebugCurrentFiber.phase = null;
5656
}
5757

58-
function setCurrentFiber(fiber: Fiber | null, phase: LifeCyclePhase | null) {
58+
function setCurrentFiber(fiber: Fiber) {
5959
ReactDebugCurrentFrame.getCurrentStack = getCurrentFiberStackAddendum;
6060
ReactDebugCurrentFiber.current = fiber;
61+
ReactDebugCurrentFiber.phase = null;
62+
}
63+
64+
function setCurrentPhase(phase: LifeCyclePhase | null) {
6165
ReactDebugCurrentFiber.phase = phase;
6266
}
6367

@@ -66,6 +70,7 @@ var ReactDebugCurrentFiber = {
6670
phase: (null: LifeCyclePhase | null),
6771
resetCurrentFiber,
6872
setCurrentFiber,
73+
setCurrentPhase,
6974
getCurrentFiberOwnerName,
7075
getCurrentFiberStackAddendum,
7176
};

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
207207

208208
if (__DEV__) {
209209
ReactCurrentOwner.current = workInProgress;
210-
ReactDebugCurrentFiber.setCurrentFiber(workInProgress, 'render');
210+
ReactDebugCurrentFiber.setCurrentPhase('render');
211211
nextChildren = fn(nextProps, context);
212-
ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null);
212+
ReactDebugCurrentFiber.setCurrentPhase(null);
213213
} else {
214214
nextChildren = fn(nextProps, context);
215215
}
@@ -281,9 +281,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
281281
ReactCurrentOwner.current = workInProgress;
282282
let nextChildren;
283283
if (__DEV__) {
284-
ReactDebugCurrentFiber.setCurrentFiber(workInProgress, 'render');
284+
ReactDebugCurrentFiber.setCurrentPhase('render');
285285
nextChildren = instance.render();
286-
ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null);
286+
ReactDebugCurrentFiber.setCurrentPhase(null);
287287
} else {
288288
nextChildren = instance.render();
289289
}
@@ -725,10 +725,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
725725
return bailoutOnLowPriority(current, workInProgress);
726726
}
727727

728-
if (__DEV__) {
729-
ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null);
730-
}
731-
732728
switch (workInProgress.tag) {
733729
case IndeterminateComponent:
734730
return mountIndeterminateComponent(

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,6 @@ var {
4242
var {Placement, Ref, Update} = ReactTypeOfSideEffect;
4343
var {OffscreenPriority} = ReactPriorityLevel;
4444

45-
if (__DEV__) {
46-
var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
47-
}
48-
4945
var invariant = require('fbjs/lib/invariant');
5046

5147
module.exports = function<T, P, I, TI, PI, C, CX, PL>(
@@ -187,10 +183,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
187183
workInProgress: Fiber,
188184
renderPriority: PriorityLevel,
189185
): Fiber | null {
190-
if (__DEV__) {
191-
ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null);
192-
}
193-
194186
// Get the latest props.
195187
let newProps = workInProgress.pendingProps;
196188
if (newProps === null) {

src/renderers/shared/fiber/ReactFiberContext.js

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,13 @@ exports.getMaskedContext = function(
8989

9090
if (__DEV__) {
9191
const name = getComponentName(workInProgress) || 'Unknown';
92-
ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null);
9392
checkPropTypes(
9493
contextTypes,
9594
context,
9695
'context',
9796
name,
9897
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
9998
);
100-
ReactDebugCurrentFiber.resetCurrentFiber();
10199
}
102100

103101
// Cache unmasked context so we can avoid recreating masked context unless necessary.
@@ -153,11 +151,7 @@ exports.pushTopLevelContextObject = function(
153151
push(didPerformWorkStackCursor, didChange, fiber);
154152
};
155153

156-
function processChildContext(
157-
fiber: Fiber,
158-
parentContext: Object,
159-
isReconciling: boolean,
160-
): Object {
154+
function processChildContext(fiber: Fiber, parentContext: Object): Object {
161155
const instance = fiber.stateNode;
162156
const childContextTypes = fiber.type.childContextTypes;
163157

@@ -184,11 +178,11 @@ function processChildContext(
184178

185179
let childContext;
186180
if (__DEV__) {
187-
ReactDebugCurrentFiber.setCurrentFiber(fiber, 'getChildContext');
181+
ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
188182
startPhaseTimer(fiber, 'getChildContext');
189183
childContext = instance.getChildContext();
190184
stopPhaseTimer();
191-
ReactDebugCurrentFiber.resetCurrentFiber();
185+
ReactDebugCurrentFiber.setCurrentPhase(null);
192186
} else {
193187
childContext = instance.getChildContext();
194188
}
@@ -202,21 +196,18 @@ function processChildContext(
202196
}
203197
if (__DEV__) {
204198
const name = getComponentName(fiber) || 'Unknown';
205-
// We can only provide accurate element stacks if we pass work-in-progress tree
206-
// during the begin or complete phase. However currently this function is also
207-
// called from unstable_renderSubtree legacy implementation. In this case it unsafe to
208-
// assume anything about the given fiber. We won't pass it down if we aren't sure.
209-
// TODO: remove this hack when we delete unstable_renderSubtree in Fiber.
210-
const workInProgress = isReconciling ? fiber : null;
211-
ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null);
212199
checkPropTypes(
213200
childContextTypes,
214201
childContext,
215202
'child context',
216203
name,
204+
// In practice, there is one case in which we won't get a stack. It's when
205+
// somebody calls unstable_renderSubtreeIntoContainer() and we process
206+
// context from the parent component instance. The stack will be missing
207+
// because it's outside of the reconciliation, and so the pointer has not
208+
// been set. This is rare and doesn't matter. We'll also remove that API.
217209
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
218210
);
219-
ReactDebugCurrentFiber.resetCurrentFiber();
220211
}
221212

222213
return {...parentContext, ...childContext};
@@ -264,11 +255,7 @@ exports.invalidateContextProvider = function(
264255
// Merge parent and own context.
265256
// Skip this if we're not updating due to sCU.
266257
// This avoids unnecessarily recomputing memoized values.
267-
const mergedContext = processChildContext(
268-
workInProgress,
269-
previousContext,
270-
true,
271-
);
258+
const mergedContext = processChildContext(workInProgress, previousContext);
272259
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
273260

274261
// Replace the old (or empty) context with the new one.

src/renderers/shared/fiber/ReactFiberReconciler.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export type Reconciler<C, I, TI> = {
181181
getContextForSubtree._injectFiber(function(fiber: Fiber) {
182182
const parentContext = findCurrentUnmaskedContext(fiber);
183183
return isContextProvider(fiber)
184-
? processChildContext(fiber, parentContext, false)
184+
? processChildContext(fiber, parentContext)
185185
: parentContext;
186186
});
187187

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
317317
function commitAllHostEffects() {
318318
while (nextEffect !== null) {
319319
if (__DEV__) {
320-
ReactDebugCurrentFiber.setCurrentFiber(nextEffect, null);
320+
ReactDebugCurrentFiber.setCurrentFiber(nextEffect);
321321
recordEffect();
322322
}
323323

@@ -615,7 +615,13 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
615615
// means that we don't need an additional field on the work in
616616
// progress.
617617
const current = workInProgress.alternate;
618+
if (__DEV__) {
619+
ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
620+
}
618621
const next = completeWork(current, workInProgress, nextPriorityLevel);
622+
if (__DEV__) {
623+
ReactDebugCurrentFiber.resetCurrentFiber();
624+
}
619625

620626
const returnFiber = workInProgress.return;
621627
const siblingFiber = workInProgress.sibling;
@@ -706,8 +712,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
706712
// See if beginning this work spawns more work.
707713
if (__DEV__) {
708714
startWorkTimer(workInProgress);
715+
ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
709716
}
710717
let next = beginWork(current, workInProgress, nextPriorityLevel);
718+
if (__DEV__) {
719+
ReactDebugCurrentFiber.resetCurrentFiber();
720+
}
711721
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
712722
ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
713723
}
@@ -718,9 +728,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
718728
}
719729

720730
ReactCurrentOwner.current = null;
721-
if (__DEV__) {
722-
ReactDebugCurrentFiber.resetCurrentFiber();
723-
}
724731

725732
return next;
726733
}
@@ -735,8 +742,12 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
735742
// See if beginning this work spawns more work.
736743
if (__DEV__) {
737744
startWorkTimer(workInProgress);
745+
ReactDebugCurrentFiber.setCurrentFiber(workInProgress);
738746
}
739747
let next = beginFailedWork(current, workInProgress, nextPriorityLevel);
748+
if (__DEV__) {
749+
ReactDebugCurrentFiber.resetCurrentFiber();
750+
}
740751
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
741752
ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
742753
}
@@ -747,9 +758,6 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
747758
}
748759

749760
ReactCurrentOwner.current = null;
750-
if (__DEV__) {
751-
ReactDebugCurrentFiber.resetCurrentFiber();
752-
}
753761

754762
return next;
755763
}

0 commit comments

Comments
 (0)