diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js index 49e107f877ef5..63cd1f97c531c 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js @@ -1749,4 +1749,114 @@ describe('ReactCompositeComponent', () => { ReactDOM.render(, container); expect(container.firstChild.tagName).toBe('DIV'); }); + + it('should not warn on updating function component from componentWillMount', () => { + let _setState; + function A() { + _setState = React.useState()[1]; + return null; + } + class B extends React.Component { + UNSAFE_componentWillMount() { + _setState({}); + } + render() { + return null; + } + } + function Parent() { + return ( +
+ + +
+ ); + } + const container = document.createElement('div'); + ReactDOM.render(, container); + }); + + it('should not warn on updating function component from componentWillUpdate', () => { + let _setState; + function A() { + _setState = React.useState()[1]; + return null; + } + class B extends React.Component { + UNSAFE_componentWillUpdate() { + _setState({}); + } + render() { + return null; + } + } + function Parent() { + return ( +
+ + +
+ ); + } + const container = document.createElement('div'); + ReactDOM.render(, container); + ReactDOM.render(, container); + }); + + it('should not warn on updating function component from componentWillReceiveProps', () => { + let _setState; + function A() { + _setState = React.useState()[1]; + return null; + } + class B extends React.Component { + UNSAFE_componentWillReceiveProps() { + _setState({}); + } + render() { + return null; + } + } + function Parent() { + return ( +
+ + +
+ ); + } + const container = document.createElement('div'); + ReactDOM.render(, container); + ReactDOM.render(, container); + }); + + it('should warn on updating function component from render', () => { + let _setState; + function A() { + _setState = React.useState()[1]; + return null; + } + class B extends React.Component { + render() { + _setState({}); + return null; + } + } + function Parent() { + return ( +
+ + +
+ ); + } + const container = document.createElement('div'); + expect(() => { + ReactDOM.render(, container); + }).toErrorDev( + 'Cannot update a component (`A`) while rendering a different component (`B`)', + ); + // Dedupe. + ReactDOM.render(, container); + }); }); diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index ddbdacd61a1dc..6d3c2abd22800 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -1356,6 +1356,7 @@ function mountIndeterminateComponent( ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null); } + setIsRendering(true); ReactCurrentOwner.current = workInProgress; value = renderWithHooks( null, @@ -1365,6 +1366,7 @@ function mountIndeterminateComponent( context, renderExpirationTime, ); + setIsRendering(false); } else { value = renderWithHooks( null, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index bec9698043db5..0ffabf3c818c3 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -2800,7 +2800,10 @@ if (__DEV__) { function warnAboutRenderPhaseUpdatesInDEV(fiber) { if (__DEV__) { - if ((executionContext & RenderContext) !== NoContext) { + if ( + ReactCurrentDebugFiberIsRenderingInDEV && + (executionContext & RenderContext) !== NoContext + ) { switch (fiber.tag) { case FunctionComponent: case ForwardRef: @@ -2808,14 +2811,14 @@ function warnAboutRenderPhaseUpdatesInDEV(fiber) { const renderingComponentName = (workInProgress && getComponentName(workInProgress.type)) || 'Unknown'; - const setStateComponentName = - getComponentName(fiber.type) || 'Unknown'; - const dedupeKey = - renderingComponentName + ' ' + setStateComponentName; + // Dedupe by the rendering component because it's the one that needs to be fixed. + const dedupeKey = renderingComponentName; if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) { didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey); + const setStateComponentName = + getComponentName(fiber.type) || 'Unknown'; console.error( - 'Cannot update a component (`%s`) from inside the function body of a ' + + 'Cannot update a component (`%s`) while rendering 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, @@ -2826,18 +2829,15 @@ function warnAboutRenderPhaseUpdatesInDEV(fiber) { break; } case ClassComponent: { - if ( - ReactCurrentDebugFiberIsRenderingInDEV && - !didWarnAboutUpdateInRender - ) { + if (!didWarnAboutUpdateInRender) { console.error( 'Cannot update during an existing state transition (such as ' + 'within `render`). Render methods should be a pure ' + 'function of props and state.', ); didWarnAboutUpdateInRender = true; - break; } + break; } } } diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js index 6de3567163a3b..c1d0ea18f3f26 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js @@ -1087,7 +1087,7 @@ describe('ReactHooks', () => { ), ).toErrorDev([ 'Context can only be read while React is rendering', - 'Cannot update a component (`Fn`) from inside the function body of a different component (`Cls`).', + 'Cannot update a component (`Fn`) while rendering a different component (`Cls`).', ]); }); @@ -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 (`%s`) from inside the function body ' + - 'of a different component (`%s`).', + 'Warning: Cannot update a component (`%s`) while rendering ' + + 'a different component (`%s`).', ); } }); diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js index 3739e655069c0..bb14e5f7b1c98 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js @@ -458,7 +458,7 @@ function loadModules({ expect(() => expect(Scheduler).toFlushAndYield(['Foo [0]', 'Bar', 'Foo [1]']), ).toErrorDev([ - 'Cannot update a component (`Foo`) from inside the function body of a ' + + 'Cannot update a component (`Foo`) while rendering a ' + 'different component (`Bar`). To locate the bad setState() call inside `Bar`', ]); });