Skip to content

Commit 95df39b

Browse files
improve error message for cross-functional component updates (#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 <[email protected]>
1 parent c0ed0a2 commit 95df39b

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
@@ -2919,17 +2919,36 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
29192919
}
29202920

29212921
let didWarnAboutUpdateInRender = false;
2922+
let didWarnAboutUpdateInRenderForAnotherComponent;
2923+
if (__DEV__) {
2924+
didWarnAboutUpdateInRenderForAnotherComponent = new Set();
2925+
}
2926+
29222927
function warnAboutRenderPhaseUpdatesInDEV(fiber) {
29232928
if (__DEV__) {
29242929
if ((executionContext & RenderContext) !== NoContext) {
29252930
switch (fiber.tag) {
29262931
case FunctionComponent:
29272932
case ForwardRef:
29282933
case SimpleMemoComponent: {
2929-
console.error(
2930-
'Cannot update a component from inside the function body of a ' +
2931-
'different component.',
2932-
);
2934+
const renderingComponentName =
2935+
(workInProgress && getComponentName(workInProgress.type)) ||
2936+
'Unknown';
2937+
const setStateComponentName =
2938+
getComponentName(fiber.type) || 'Unknown';
2939+
const dedupeKey =
2940+
renderingComponentName + ' ' + setStateComponentName;
2941+
if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) {
2942+
didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey);
2943+
console.error(
2944+
'Cannot update a component (`%s`) from inside the function body of a ' +
2945+
'different component (`%s`). To locate the bad setState() call inside `%s`, ' +
2946+
'follow the stack trace as described in https://fb.me/setstate-in-render',
2947+
setStateComponentName,
2948+
renderingComponentName,
2949+
renderingComponentName,
2950+
);
2951+
}
29332952
break;
29342953
}
29352954
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
@@ -412,7 +412,7 @@ describe('ReactHooksWithNoopRenderer', () => {
412412

413413
function Bar({triggerUpdate}) {
414414
if (triggerUpdate) {
415-
setStep(1);
415+
setStep(x => x + 1);
416416
}
417417
return <Text text="Bar" />;
418418
}
@@ -440,10 +440,21 @@ describe('ReactHooksWithNoopRenderer', () => {
440440
expect(() =>
441441
expect(Scheduler).toFlushAndYield(['Foo [0]', 'Bar', 'Foo [1]']),
442442
).toErrorDev([
443-
'Cannot update a component from inside the function body of a ' +
444-
'different component.',
443+
'Cannot update a component (`Foo`) from inside the function body of a ' +
444+
'different component (`Bar`). To locate the bad setState() call inside `Bar`',
445445
]);
446446
});
447+
448+
// It should not warn again (deduplication).
449+
await ReactNoop.act(async () => {
450+
root.render(
451+
<>
452+
<Foo />
453+
<Bar triggerUpdate={true} />
454+
</>,
455+
);
456+
expect(Scheduler).toFlushAndYield(['Foo [1]', 'Bar', 'Foo [2]']);
457+
});
447458
});
448459

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

0 commit comments

Comments
 (0)