Skip to content

Commit 7554ab7

Browse files
JoviDeCroockgaearon
authored andcommitted
improve error message for cross-functional component updates (facebook#18316)
* improve error message for cross-functional component updates * correctly use %s by quoting it * use workInProgress and lint * add test assertion * fix test * Improve the error message Co-authored-by: Dan Abramov <dan.abramov@me.com>
1 parent 2186beb commit 7554ab7

File tree

3 files changed

+40
-10
lines changed

3 files changed

+40
-10
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2793,17 +2793,36 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
27932793
}
27942794

27952795
let didWarnAboutUpdateInRender = false;
2796+
let didWarnAboutUpdateInRenderForAnotherComponent;
2797+
if (__DEV__) {
2798+
didWarnAboutUpdateInRenderForAnotherComponent = new Set();
2799+
}
2800+
27962801
function warnAboutRenderPhaseUpdatesInDEV(fiber) {
27972802
if (__DEV__) {
27982803
if ((executionContext & RenderContext) !== NoContext) {
27992804
switch (fiber.tag) {
28002805
case FunctionComponent:
28012806
case ForwardRef:
28022807
case SimpleMemoComponent: {
2803-
console.error(
2804-
'Cannot update a component from inside the function body of a ' +
2805-
'different component.',
2806-
);
2808+
const renderingComponentName =
2809+
(workInProgress && getComponentName(workInProgress.type)) ||
2810+
'Unknown';
2811+
const setStateComponentName =
2812+
getComponentName(fiber.type) || 'Unknown';
2813+
const dedupeKey =
2814+
renderingComponentName + ' ' + setStateComponentName;
2815+
if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) {
2816+
didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey);
2817+
console.error(
2818+
'Cannot update a component (`%s`) from inside the function body of a ' +
2819+
'different component (`%s`). To locate the bad setState() call inside `%s`, ' +
2820+
'follow the stack trace as described in https://fb.me/setstate-in-render',
2821+
setStateComponentName,
2822+
renderingComponentName,
2823+
renderingComponentName,
2824+
);
2825+
}
28072826
break;
28082827
}
28092828
case ClassComponent: {

packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,7 +1087,7 @@ describe('ReactHooks', () => {
10871087
),
10881088
).toErrorDev([
10891089
'Context can only be read while React is rendering',
1090-
'Cannot update a component from inside the function body of a different component.',
1090+
'Cannot update a component (`Fn`) from inside the function body of a different component (`Cls`).',
10911091
]);
10921092
});
10931093

@@ -1783,8 +1783,8 @@ describe('ReactHooks', () => {
17831783
if (__DEV__) {
17841784
expect(console.error).toHaveBeenCalledTimes(2);
17851785
expect(console.error.calls.argsFor(0)[0]).toContain(
1786-
'Warning: Cannot update a component from inside the function body ' +
1787-
'of a different component.%s',
1786+
'Warning: Cannot update a component (`%s`) from inside the function body ' +
1787+
'of a different component (`%s`).',
17881788
);
17891789
}
17901790
});

packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ function loadModules({
430430

431431
function Bar({triggerUpdate}) {
432432
if (triggerUpdate) {
433-
setStep(1);
433+
setStep(x => x + 1);
434434
}
435435
return <Text text="Bar" />;
436436
}
@@ -458,10 +458,21 @@ function loadModules({
458458
expect(() =>
459459
expect(Scheduler).toFlushAndYield(['Foo [0]', 'Bar', 'Foo [1]']),
460460
).toErrorDev([
461-
'Cannot update a component from inside the function body of a ' +
462-
'different component.',
461+
'Cannot update a component (`Foo`) from inside the function body of a ' +
462+
'different component (`Bar`). To locate the bad setState() call inside `Bar`',
463463
]);
464464
});
465+
466+
// It should not warn again (deduplication).
467+
await ReactNoop.act(async () => {
468+
root.render(
469+
<>
470+
<Foo />
471+
<Bar triggerUpdate={true} />
472+
</>,
473+
);
474+
expect(Scheduler).toFlushAndYield(['Foo [1]', 'Bar', 'Foo [2]']);
475+
});
465476
});
466477

467478
it('keeps restarting until there are no more new updates', () => {

0 commit comments

Comments
 (0)