Skip to content

Commit

Permalink
improve error message for cross-functional component updates (faceboo…
Browse files Browse the repository at this point in the history
…k#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>
  • Loading branch information
2 people authored and acdlite committed Mar 19, 2020
1 parent 82cf50a commit 8205a00
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 10 deletions.
27 changes: 23 additions & 4 deletions packages/react-reconciler/src/ReactFiberWorkLoop.js
Expand Up @@ -2794,17 +2794,36 @@ if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {

let didWarnAboutUpdateInRender = false;
let didWarnAboutUpdateInGetChildContext = false;
let didWarnAboutUpdateInRenderForAnotherComponent;
if (__DEV__) {
didWarnAboutUpdateInRenderForAnotherComponent = new Set();
}

function warnAboutRenderPhaseUpdatesInDEV(fiber) {
if (__DEV__) {
if ((executionContext & RenderContext) !== NoContext) {
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
console.error(
'Cannot update a component from inside the function body of a ' +
'different component.',
);
const renderingComponentName =
(workInProgress && getComponentName(workInProgress.type)) ||
'Unknown';
const setStateComponentName =
getComponentName(fiber.type) || 'Unknown';
const dedupeKey =
renderingComponentName + ' ' + setStateComponentName;
if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) {
didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey);
console.error(
'Cannot update a component (`%s`) from inside the function body of a ' +
'different component (`%s`). To locate the bad setState() call inside `%s`, ' +
'follow the stack trace as described in https://fb.me/setstate-in-render',
setStateComponentName,
renderingComponentName,
renderingComponentName,
);
}
break;
}
case ClassComponent: {
Expand Down
Expand Up @@ -1087,7 +1087,7 @@ describe('ReactHooks', () => {
),
).toErrorDev([
'Context can only be read while React is rendering',
'Cannot update a component from inside the function body of a different component.',
'Cannot update a component (`Fn`) from inside the function body of a different component (`Cls`).',
]);
});

Expand Down Expand Up @@ -1783,8 +1783,8 @@ describe('ReactHooks', () => {
if (__DEV__) {
expect(console.error).toHaveBeenCalledTimes(2);
expect(console.error.calls.argsFor(0)[0]).toContain(
'Warning: Cannot update a component from inside the function body ' +
'of a different component.%s',
'Warning: Cannot update a component (`%s`) from inside the function body ' +
'of a different component (`%s`).',
);
}
});
Expand Down
Expand Up @@ -430,7 +430,7 @@ function loadModules({

function Bar({triggerUpdate}) {
if (triggerUpdate) {
setStep(1);
setStep(x => x + 1);
}
return <Text text="Bar" />;
}
Expand Down Expand Up @@ -458,10 +458,21 @@ function loadModules({
expect(() =>
expect(Scheduler).toFlushAndYield(['Foo [0]', 'Bar', 'Foo [1]']),
).toErrorDev([
'Cannot update a component from inside the function body of a ' +
'different component.',
'Cannot update a component (`Foo`) from inside the function body of a ' +
'different component (`Bar`). To locate the bad setState() call inside `Bar`',
]);
});

// It should not warn again (deduplication).
await ReactNoop.act(async () => {
root.render(
<>
<Foo />
<Bar triggerUpdate={true} />
</>,
);
expect(Scheduler).toFlushAndYield(['Foo [1]', 'Bar', 'Foo [2]']);
});
});

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

0 comments on commit 8205a00

Please sign in to comment.