@samitouri / QOS-React-2 / commits / fe1f79b95b

Don't fire the render phase update warning for class lifecycles (#18330)

* Change the warning to not say "function body" This warning is more generic and may happen with class components too. * Dedupe by the rendering component * Don't warn outside of render

Dan Abramov committed Mar 18, 2020 at 00:07 UTC fe1f79b95b5987b7179d1eaf5ff5bb9a7bc4b7b5
5 files changed +127 -15
packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
+110
@@ -1749,4 +1749,114 @@ describe('ReactCompositeComponent', () => {
1749 ReactDOM.render(<Shadow />, container);
1750 expect(container.firstChild.tagName).toBe('DIV');
1751 });
1752 +
1753 + it('should not warn on updating function component from componentWillMount', () => {
1754 + let _setState;
1755 + function A() {
1756 + _setState = React.useState()[1];
1757 + return null;
1758 + }
1759 + class B extends React.Component {
1760 + UNSAFE_componentWillMount() {
1761 + _setState({});
1762 + }
1763 + render() {
1764 + return null;
1765 + }
1766 + }
1767 + function Parent() {
1768 + return (
1769 + <div>
1770 + <A />
1771 + <B />
1772 + </div>
1773 + );
1774 + }
1775 + const container = document.createElement('div');
1776 + ReactDOM.render(<Parent />, container);
1777 + });
1778 +
1779 + it('should not warn on updating function component from componentWillUpdate', () => {
1780 + let _setState;
1781 + function A() {
1782 + _setState = React.useState()[1];
1783 + return null;
1784 + }
1785 + class B extends React.Component {
1786 + UNSAFE_componentWillUpdate() {
1787 + _setState({});
1788 + }
1789 + render() {
1790 + return null;
1791 + }
1792 + }
1793 + function Parent() {
1794 + return (
1795 + <div>
1796 + <A />
1797 + <B />
1798 + </div>
1799 + );
1800 + }
1801 + const container = document.createElement('div');
1802 + ReactDOM.render(<Parent />, container);
1803 + ReactDOM.render(<Parent />, container);
1804 + });
1805 +
1806 + it('should not warn on updating function component from componentWillReceiveProps', () => {
1807 + let _setState;
1808 + function A() {
1809 + _setState = React.useState()[1];
1810 + return null;
1811 + }
1812 + class B extends React.Component {
1813 + UNSAFE_componentWillReceiveProps() {
1814 + _setState({});
1815 + }
1816 + render() {
1817 + return null;
1818 + }
1819 + }
1820 + function Parent() {
1821 + return (
1822 + <div>
1823 + <A />
1824 + <B />
1825 + </div>
1826 + );
1827 + }
1828 + const container = document.createElement('div');
1829 + ReactDOM.render(<Parent />, container);
1830 + ReactDOM.render(<Parent />, container);
1831 + });
1832 +
1833 + it('should warn on updating function component from render', () => {
1834 + let _setState;
1835 + function A() {
1836 + _setState = React.useState()[1];
1837 + return null;
1838 + }
1839 + class B extends React.Component {
1840 + render() {
1841 + _setState({});
1842 + return null;
1843 + }
1844 + }
1845 + function Parent() {
1846 + return (
1847 + <div>
1848 + <A />
1849 + <B />
1850 + </div>
1851 + );
1852 + }
1853 + const container = document.createElement('div');
1854 + expect(() => {
1855 + ReactDOM.render(<Parent />, container);
1856 + }).toErrorDev(
1857 + 'Cannot update a component (`A`) while rendering a different component (`B`)',
1858 + );
1859 + // Dedupe.
1860 + ReactDOM.render(<Parent />, container);
1861 + });
1862 });
packages/react-reconciler/src/ReactFiberBeginWork.js
+2
@@ -1363,6 +1363,7 @@ function mountIndeterminateComponent(
1363 ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
1364 }
1365
1366 + setIsRendering(true);
1367 ReactCurrentOwner.current = workInProgress;
1368 value = renderWithHooks(
1369 null,
@@ -1372,6 +1373,7 @@ function mountIndeterminateComponent(
1373 context,
1374 renderExpirationTime,
1375 );
1376 + setIsRendering(false);
1377 } else {
1378 value = renderWithHooks(
1379 null,
packages/react-reconciler/src/ReactFiberWorkLoop.js
+11 -11
@@ -2927,7 +2927,10 @@ if (__DEV__) {
2927
2928 function warnAboutRenderPhaseUpdatesInDEV(fiber) {
2929 if (__DEV__) {
2930 - if ((executionContext & RenderContext) !== NoContext) {
2930 + if (
2931 + ReactCurrentDebugFiberIsRenderingInDEV &&
2932 + (executionContext & RenderContext) !== NoContext
2933 + ) {
2934 switch (fiber.tag) {
2935 case FunctionComponent:
2936 case ForwardRef:
@@ -2935,14 +2938,14 @@ function warnAboutRenderPhaseUpdatesInDEV(fiber) {
2938 const renderingComponentName =
2939 (workInProgress && getComponentName(workInProgress.type)) ||
2940 'Unknown';
2938 - const setStateComponentName =
2939 - getComponentName(fiber.type) || 'Unknown';
2940 - const dedupeKey =
2941 - renderingComponentName + ' ' + setStateComponentName;
2941 + // Dedupe by the rendering component because it's the one that needs to be fixed.
2942 + const dedupeKey = renderingComponentName;
2943 if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) {
2944 didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey);
2945 + const setStateComponentName =
2946 + getComponentName(fiber.type) || 'Unknown';
2947 console.error(
2945 - 'Cannot update a component (`%s`) from inside the function body of a ' +
2948 + 'Cannot update a component (`%s`) while rendering a ' +
2949 'different component (`%s`). To locate the bad setState() call inside `%s`, ' +
2950 'follow the stack trace as described in https://fb.me/setstate-in-render',
2951 setStateComponentName,
@@ -2953,18 +2956,15 @@ function warnAboutRenderPhaseUpdatesInDEV(fiber) {
2956 break;
2957 }
2958 case ClassComponent: {
2956 - if (
2957 - ReactCurrentDebugFiberIsRenderingInDEV &&
2958 - !didWarnAboutUpdateInRender
2959 - ) {
2959 + if (!didWarnAboutUpdateInRender) {
2960 console.error(
2961 'Cannot update during an existing state transition (such as ' +
2962 'within `render`). Render methods should be a pure ' +
2963 'function of props and state.',
2964 );
2965 didWarnAboutUpdateInRender = true;
2966 - break;
2966 }
2967 + break;
2968 }
2969 }
2970 }
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+3 -3
@@ -1087,7 +1087,7 @@ describe('ReactHooks', () => {
1087 ),
1088 ).toErrorDev([
1089 'Context can only be read while React is rendering',
1090 - 'Cannot update a component (`Fn`) from inside the function body of a different component (`Cls`).',
1090 + 'Cannot update a component (`Fn`) while rendering a different component (`Cls`).',
1091 ]);
1092 });
1093
@@ -1783,8 +1783,8 @@ describe('ReactHooks', () => {
1783 if (__DEV__) {
1784 expect(console.error).toHaveBeenCalledTimes(2);
1785 expect(console.error.calls.argsFor(0)[0]).toContain(
1786 - 'Warning: Cannot update a component (`%s`) from inside the function body ' +
1787 - 'of a different component (`%s`).',
1786 + 'Warning: Cannot update a component (`%s`) while rendering ' +
1787 + 'a different component (`%s`).',
1788 );
1789 }
1790 });
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
+1 -1
@@ -476,7 +476,7 @@ describe('ReactHooksWithNoopRenderer', () => {
476 expect(() =>
477 expect(Scheduler).toFlushAndYield(['Foo [0]', 'Bar', 'Foo [1]']),
478 ).toErrorDev([
479 - 'Cannot update a component (`Foo`) from inside the function body of a ' +
479 + 'Cannot update a component (`Foo`) while rendering a ' +
480 'different component (`Bar`). To locate the bad setState() call inside `Bar`',
481 ]);
482 });