@samitouri / QOS-React / commits / d8d2b6e89c

Disable module components dynamically for WWW (#18446)

* Make disableModulePatternComponents dynamic for WWW * Run both flags and tests and respect the flag in SSR

Dan Abramov committed Apr 1, 2020 at 18:31 UTC d8d2b6e89cdff27a1ac246c6e9e030c2cc8760e3
18 files changed +505 -402
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+57 -55
@@ -983,64 +983,66 @@ describe('ReactComponentLifeCycle', () => {
983 });
984 });
985
986 - it('calls effects on module-pattern component', function() {
987 - const log = [];
986 + if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
987 + it('calls effects on module-pattern component', function() {
988 + const log = [];
989
989 - function Parent() {
990 - return {
991 - render() {
992 - expect(typeof this.props).toBe('object');
993 - log.push('render');
994 - return <Child />;
995 - },
996 - UNSAFE_componentWillMount() {
997 - log.push('will mount');
998 - },
999 - componentDidMount() {
1000 - log.push('did mount');
1001 - },
1002 - componentDidUpdate() {
1003 - log.push('did update');
1004 - },
1005 - getChildContext() {
1006 - return {x: 2};
1007 - },
990 + function Parent() {
991 + return {
992 + render() {
993 + expect(typeof this.props).toBe('object');
994 + log.push('render');
995 + return <Child />;
996 + },
997 + UNSAFE_componentWillMount() {
998 + log.push('will mount');
999 + },
1000 + componentDidMount() {
1001 + log.push('did mount');
1002 + },
1003 + componentDidUpdate() {
1004 + log.push('did update');
1005 + },
1006 + getChildContext() {
1007 + return {x: 2};
1008 + },
1009 + };
1010 + }
1011 + Parent.childContextTypes = {
1012 + x: PropTypes.number,
1013 + };
1014 + function Child(props, context) {
1015 + expect(context.x).toBe(2);
1016 + return <div />;
1017 + }
1018 + Child.contextTypes = {
1019 + x: PropTypes.number,
1020 };
1009 - }
1010 - Parent.childContextTypes = {
1011 - x: PropTypes.number,
1012 - };
1013 - function Child(props, context) {
1014 - expect(context.x).toBe(2);
1015 - return <div />;
1016 - }
1017 - Child.contextTypes = {
1018 - x: PropTypes.number,
1019 - };
1020 -
1021 - const div = document.createElement('div');
1022 - expect(() =>
1023 - ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div),
1024 - ).toErrorDev(
1025 - 'Warning: The <Parent /> component appears to be a function component that returns a class instance. ' +
1026 - 'Change Parent to a class that extends React.Component instead. ' +
1027 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
1028 - '`Parent.prototype = React.Component.prototype`. ' +
1029 - "Don't use an arrow function since it cannot be called with `new` by React.",
1030 - );
1031 - ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
1032 -
1033 - expect(log).toEqual([
1034 - 'will mount',
1035 - 'render',
1036 - 'did mount',
1037 - 'ref',
1021
1039 - 'render',
1040 - 'did update',
1041 - 'ref',
1042 - ]);
1043 - });
1022 + const div = document.createElement('div');
1023 + expect(() =>
1024 + ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div),
1025 + ).toErrorDev(
1026 + 'Warning: The <Parent /> component appears to be a function component that returns a class instance. ' +
1027 + 'Change Parent to a class that extends React.Component instead. ' +
1028 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
1029 + '`Parent.prototype = React.Component.prototype`. ' +
1030 + "Don't use an arrow function since it cannot be called with `new` by React.",
1031 + );
1032 + ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
1033 +
1034 + expect(log).toEqual([
1035 + 'will mount',
1036 + 'render',
1037 + 'did mount',
1038 + 'ref',
1039 +
1040 + 'render',
1041 + 'did update',
1042 + 'ref',
1043 + ]);
1044 + });
1045 + }
1046
1047 it('should warn if getDerivedStateFromProps returns undefined', () => {
1048 class MyComponent extends React.Component {
packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
+45 -18
@@ -108,26 +108,53 @@ describe('ReactCompositeComponent', () => {
108 };
109 });
110
111 - it('should support module pattern components', () => {
112 - function Child({test}) {
113 - return {
114 - render() {
115 - return <div>{test}</div>;
116 - },
117 - };
118 - }
111 + if (require('shared/ReactFeatureFlags').disableModulePatternComponents) {
112 + it('should not support module pattern components', () => {
113 + function Child({test}) {
114 + return {
115 + render() {
116 + return <div>{test}</div>;
117 + },
118 + };
119 + }
120
120 - const el = document.createElement('div');
121 - expect(() => ReactDOM.render(<Child test="test" />, el)).toErrorDev(
122 - 'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
123 - 'Change Child to a class that extends React.Component instead. ' +
124 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
125 - '`Child.prototype = React.Component.prototype`. ' +
126 - "Don't use an arrow function since it cannot be called with `new` by React.",
127 - );
121 + const el = document.createElement('div');
122 + expect(() => {
123 + expect(() => ReactDOM.render(<Child test="test" />, el)).toThrow(
124 + 'Objects are not valid as a React child (found: object with keys {render}).',
125 + );
126 + }).toErrorDev(
127 + 'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
128 + 'Change Child to a class that extends React.Component instead. ' +
129 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
130 + '`Child.prototype = React.Component.prototype`. ' +
131 + "Don't use an arrow function since it cannot be called with `new` by React.",
132 + );
133
129 - expect(el.textContent).toBe('test');
130 - });
134 + expect(el.textContent).toBe('');
135 + });
136 + } else {
137 + it('should support module pattern components', () => {
138 + function Child({test}) {
139 + return {
140 + render() {
141 + return <div>{test}</div>;
142 + },
143 + };
144 + }
145 +
146 + const el = document.createElement('div');
147 + expect(() => ReactDOM.render(<Child test="test" />, el)).toErrorDev(
148 + 'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
149 + 'Change Child to a class that extends React.Component instead. ' +
150 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
151 + '`Child.prototype = React.Component.prototype`. ' +
152 + "Don't use an arrow function since it cannot be called with `new` by React.",
153 + );
154 +
155 + expect(el.textContent).toBe('test');
156 + });
157 + }
158
159 it('should support rendering to different child types over time', () => {
160 const instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js
+46 -44
@@ -459,55 +459,57 @@ describe('ReactCompositeComponent-state', () => {
459 ]);
460 });
461
462 - it('should support stateful module pattern components', () => {
463 - function Child() {
464 - return {
465 - state: {
466 - count: 123,
467 - },
468 - render() {
469 - return <div>{`count:${this.state.count}`}</div>;
470 - },
471 - };
472 - }
462 + if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
463 + it('should support stateful module pattern components', () => {
464 + function Child() {
465 + return {
466 + state: {
467 + count: 123,
468 + },
469 + render() {
470 + return <div>{`count:${this.state.count}`}</div>;
471 + },
472 + };
473 + }
474
474 - const el = document.createElement('div');
475 - expect(() => ReactDOM.render(<Child />, el)).toErrorDev(
476 - 'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
477 - 'Change Child to a class that extends React.Component instead. ' +
478 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
479 - '`Child.prototype = React.Component.prototype`. ' +
480 - "Don't use an arrow function since it cannot be called with `new` by React.",
481 - );
475 + const el = document.createElement('div');
476 + expect(() => ReactDOM.render(<Child />, el)).toErrorDev(
477 + 'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
478 + 'Change Child to a class that extends React.Component instead. ' +
479 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
480 + '`Child.prototype = React.Component.prototype`. ' +
481 + "Don't use an arrow function since it cannot be called with `new` by React.",
482 + );
483
483 - expect(el.textContent).toBe('count:123');
484 - });
484 + expect(el.textContent).toBe('count:123');
485 + });
486
486 - it('should support getDerivedStateFromProps for module pattern components', () => {
487 - function Child() {
488 - return {
489 - state: {
490 - count: 1,
491 - },
492 - render() {
493 - return <div>{`count:${this.state.count}`}</div>;
494 - },
495 - };
496 - }
497 - Child.getDerivedStateFromProps = (props, prevState) => {
498 - return {
499 - count: prevState.count + props.incrementBy,
487 + it('should support getDerivedStateFromProps for module pattern components', () => {
488 + function Child() {
489 + return {
490 + state: {
491 + count: 1,
492 + },
493 + render() {
494 + return <div>{`count:${this.state.count}`}</div>;
495 + },
496 + };
497 + }
498 + Child.getDerivedStateFromProps = (props, prevState) => {
499 + return {
500 + count: prevState.count + props.incrementBy,
501 + };
502 };
501 - };
503
503 - const el = document.createElement('div');
504 - ReactDOM.render(<Child incrementBy={0} />, el);
505 - expect(el.textContent).toBe('count:1');
504 + const el = document.createElement('div');
505 + ReactDOM.render(<Child incrementBy={0} />, el);
506 + expect(el.textContent).toBe('count:1');
507
507 - ReactDOM.render(<Child incrementBy={2} />, el);
508 - expect(el.textContent).toBe('count:3');
508 + ReactDOM.render(<Child incrementBy={2} />, el);
509 + expect(el.textContent).toBe('count:3');
510
510 - ReactDOM.render(<Child incrementBy={1} />, el);
511 - expect(el.textContent).toBe('count:4');
512 - });
511 + ReactDOM.render(<Child incrementBy={1} />, el);
512 + expect(el.textContent).toBe('count:4');
513 + });
514 + }
515 });
packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js
+26 -9
@@ -644,16 +644,33 @@ describe('ReactDOMServerIntegration', () => {
644 checkFooDiv(await render(<ClassComponent />));
645 });
646
647 - itRenders('factory components', async render => {
648 - const FactoryComponent = () => {
649 - return {
650 - render: function() {
651 - return <div>foo</div>;
652 - },
647 + if (require('shared/ReactFeatureFlags').disableModulePatternComponents) {
648 + itThrowsWhenRendering(
649 + 'factory components',
650 + async render => {
651 + const FactoryComponent = () => {
652 + return {
653 + render: function() {
654 + return <div>foo</div>;
655 + },
656 + };
657 + };
658 + await render(<FactoryComponent />, 1);
659 + },
660 + 'Objects are not valid as a React child (found: object with keys {render})',
661 + );
662 + } else {
663 + itRenders('factory components', async render => {
664 + const FactoryComponent = () => {
665 + return {
666 + render: function() {
667 + return <div>foo</div>;
668 + },
669 + };
670 };
654 - };
655 - checkFooDiv(await render(<FactoryComponent />, 1));
656 - });
671 + checkFooDiv(await render(<FactoryComponent />, 1));
672 + });
673 + }
674 });
675
676 describe('component hierarchies', function() {
packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
+36 -34
@@ -782,43 +782,45 @@ describe('ReactErrorBoundaries', () => {
782 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
783 });
784
785 - it('renders an error state if module-style context provider throws in componentWillMount', () => {
786 - function BrokenComponentWillMountWithContext() {
787 - return {
788 - getChildContext() {
789 - return {foo: 42};
790 - },
791 - render() {
792 - return <div>{this.props.children}</div>;
793 - },
794 - UNSAFE_componentWillMount() {
795 - throw new Error('Hello');
796 - },
785 + if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
786 + it('renders an error state if module-style context provider throws in componentWillMount', () => {
787 + function BrokenComponentWillMountWithContext() {
788 + return {
789 + getChildContext() {
790 + return {foo: 42};
791 + },
792 + render() {
793 + return <div>{this.props.children}</div>;
794 + },
795 + UNSAFE_componentWillMount() {
796 + throw new Error('Hello');
797 + },
798 + };
799 + }
800 + BrokenComponentWillMountWithContext.childContextTypes = {
801 + foo: PropTypes.number,
802 };
798 - }
799 - BrokenComponentWillMountWithContext.childContextTypes = {
800 - foo: PropTypes.number,
801 - };
803
803 - const container = document.createElement('div');
804 - expect(() =>
805 - ReactDOM.render(
806 - <ErrorBoundary>
807 - <BrokenComponentWillMountWithContext />
808 - </ErrorBoundary>,
809 - container,
810 - ),
811 - ).toErrorDev(
812 - 'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
813 - 'returns a class instance. ' +
814 - 'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
815 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
816 - '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
817 - "Don't use an arrow function since it cannot be called with `new` by React.",
818 - );
804 + const container = document.createElement('div');
805 + expect(() =>
806 + ReactDOM.render(
807 + <ErrorBoundary>
808 + <BrokenComponentWillMountWithContext />
809 + </ErrorBoundary>,
810 + container,
811 + ),
812 + ).toErrorDev(
813 + 'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
814 + 'returns a class instance. ' +
815 + 'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
816 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
817 + '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
818 + "Don't use an arrow function since it cannot be called with `new` by React.",
819 + );
820
820 - expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
821 - });
821 + expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
822 + });
823 + }
824
825 it('mounts the error message if mounting fails', () => {
826 function renderError(error) {
packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js
+36 -34
@@ -814,42 +814,44 @@ describe('ReactLegacyErrorBoundaries', () => {
814 expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
815 });
816
817 - it('renders an error state if module-style context provider throws in componentWillMount', () => {
818 - function BrokenComponentWillMountWithContext() {
819 - return {
820 - getChildContext() {
821 - return {foo: 42};
822 - },
823 - render() {
824 - return <div>{this.props.children}</div>;
825 - },
826 - UNSAFE_componentWillMount() {
827 - throw new Error('Hello');
828 - },
817 + if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
818 + it('renders an error state if module-style context provider throws in componentWillMount', () => {
819 + function BrokenComponentWillMountWithContext() {
820 + return {
821 + getChildContext() {
822 + return {foo: 42};
823 + },
824 + render() {
825 + return <div>{this.props.children}</div>;
826 + },
827 + UNSAFE_componentWillMount() {
828 + throw new Error('Hello');
829 + },
830 + };
831 + }
832 + BrokenComponentWillMountWithContext.childContextTypes = {
833 + foo: PropTypes.number,
834 };
830 - }
831 - BrokenComponentWillMountWithContext.childContextTypes = {
832 - foo: PropTypes.number,
833 - };
835
835 - const container = document.createElement('div');
836 - expect(() =>
837 - ReactDOM.render(
838 - <ErrorBoundary>
839 - <BrokenComponentWillMountWithContext />
840 - </ErrorBoundary>,
841 - container,
842 - ),
843 - ).toErrorDev(
844 - 'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
845 - 'returns a class instance. ' +
846 - 'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
847 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
848 - '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
849 - "Don't use an arrow function since it cannot be called with `new` by React.",
850 - );
851 - expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
852 - });
836 + const container = document.createElement('div');
837 + expect(() =>
838 + ReactDOM.render(
839 + <ErrorBoundary>
840 + <BrokenComponentWillMountWithContext />
841 + </ErrorBoundary>,
842 + container,
843 + ),
844 + ).toErrorDev(
845 + 'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
846 + 'returns a class instance. ' +
847 + 'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
848 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
849 + '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
850 + "Don't use an arrow function since it cannot be called with `new` by React.",
851 + );
852 + expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
853 + });
854 + }
855
856 it('mounts the error message if mounting fails', () => {
857 function renderError(error) {
packages/react-dom/src/__tests__/refs-test.js
+23 -21
@@ -156,29 +156,31 @@ describe('reactiverefs', () => {
156 });
157 });
158
159 -describe('factory components', () => {
160 - it('Should correctly get the ref', () => {
161 - function Comp() {
162 - return {
163 - render() {
164 - return <div ref="elemRef" />;
165 - },
166 - };
167 - }
159 +if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
160 + describe('factory components', () => {
161 + it('Should correctly get the ref', () => {
162 + function Comp() {
163 + return {
164 + render() {
165 + return <div ref="elemRef" />;
166 + },
167 + };
168 + }
169
169 - let inst;
170 - expect(
171 - () => (inst = ReactTestUtils.renderIntoDocument(<Comp />)),
172 - ).toErrorDev(
173 - 'Warning: The <Comp /> component appears to be a function component that returns a class instance. ' +
174 - 'Change Comp to a class that extends React.Component instead. ' +
175 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
176 - '`Comp.prototype = React.Component.prototype`. ' +
177 - "Don't use an arrow function since it cannot be called with `new` by React.",
178 - );
179 - expect(inst.refs.elemRef.tagName).toBe('DIV');
170 + let inst;
171 + expect(
172 + () => (inst = ReactTestUtils.renderIntoDocument(<Comp />)),
173 + ).toErrorDev(
174 + 'Warning: The <Comp /> component appears to be a function component that returns a class instance. ' +
175 + 'Change Comp to a class that extends React.Component instead. ' +
176 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
177 + '`Comp.prototype = React.Component.prototype`. ' +
178 + "Don't use an arrow function since it cannot be called with `new` by React.",
179 + );
180 + expect(inst.refs.elemRef.tagName).toBe('DIV');
181 + });
182 });
181 -});
183 +}
184
185 /**
186 * Tests that when a ref hops around children, we can track that correctly.
packages/react-dom/src/server/ReactPartialRenderer.js
+29 -18
@@ -20,6 +20,7 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
20 import {
21 warnAboutDeprecatedLifecycles,
22 disableLegacyContext,
23 + disableModulePatternComponents,
24 enableSuspenseServerRenderer,
25 enableFundamentalAPI,
26 enableDeprecatedFlareAPI,
@@ -527,28 +528,38 @@ function resolve(
528 inst = Component(element.props, publicContext, updater);
529 inst = finishHooks(Component, element.props, inst, publicContext);
530
530 - if (inst == null || inst.render == null) {
531 + if (__DEV__) {
532 + // Support for module components is deprecated and is removed behind a flag.
533 + // Whether or not it would crash later, we want to show a good message in DEV first.
534 + if (inst != null && inst.render != null) {
535 + const componentName = getComponentName(Component) || 'Unknown';
536 + if (!didWarnAboutModulePatternComponent[componentName]) {
537 + console.error(
538 + 'The <%s /> component appears to be a function component that returns a class instance. ' +
539 + 'Change %s to a class that extends React.Component instead. ' +
540 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
541 + "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
542 + 'cannot be called with `new` by React.',
543 + componentName,
544 + componentName,
545 + componentName,
546 + );
547 + didWarnAboutModulePatternComponent[componentName] = true;
548 + }
549 + }
550 + }
551 +
552 + // If the flag is on, everything is assumed to be a function component.
553 + // Otherwise, we also do the unfortunate dynamic checks.
554 + if (
555 + disableModulePatternComponents ||
556 + inst == null ||
557 + inst.render == null
558 + ) {
559 child = inst;
560 validateRenderResult(child, Component);
561 return;
562 }
535 -
536 - if (__DEV__) {
537 - const componentName = getComponentName(Component) || 'Unknown';
538 - if (!didWarnAboutModulePatternComponent[componentName]) {
539 - console.error(
540 - 'The <%s /> component appears to be a function component that returns a class instance. ' +
541 - 'Change %s to a class that extends React.Component instead. ' +
542 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
543 - "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
544 - 'cannot be called with `new` by React.',
545 - componentName,
546 - componentName,
547 - componentName,
548 - );
549 - didWarnAboutModulePatternComponent[componentName] = true;
550 - }
551 - }
563 }
564
565 inst.props = element.props;
packages/react-reconciler/src/ReactFiberBeginWork.js
+28
@@ -1374,7 +1374,35 @@ function mountIndeterminateComponent(
1374 // React DevTools reads this flag.
1375 workInProgress.effectTag |= PerformedWork;
1376
1377 + if (__DEV__) {
1378 + // Support for module components is deprecated and is removed behind a flag.
1379 + // Whether or not it would crash later, we want to show a good message in DEV first.
1380 + if (
1381 + typeof value === 'object' &&
1382 + value !== null &&
1383 + typeof value.render === 'function' &&
1384 + value.$$typeof === undefined
1385 + ) {
1386 + const componentName = getComponentName(Component) || 'Unknown';
1387 + if (!didWarnAboutModulePatternComponent[componentName]) {
1388 + console.error(
1389 + 'The <%s /> component appears to be a function component that returns a class instance. ' +
1390 + 'Change %s to a class that extends React.Component instead. ' +
1391 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
1392 + "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
1393 + 'cannot be called with `new` by React.',
1394 + componentName,
1395 + componentName,
1396 + componentName,
1397 + );
1398 + didWarnAboutModulePatternComponent[componentName] = true;
1399 + }
1400 + }
1401 + }
1402 +
1403 if (
1404 + // Run these checks in production only if the flag is off.
1405 + // Eventually we'll delete this branch altogether.
1406 !disableModulePatternComponents &&
1407 typeof value === 'object' &&
1408 value !== null &&
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+30 -26
@@ -1237,6 +1237,7 @@ describe('ReactHooks', () => {
1237 'Context can only be read while React is rendering',
1238 );
1239 });
1240 +
1241 it('double-invokes components with Hooks in Strict Mode', () => {
1242 ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true;
1243
@@ -1351,32 +1352,35 @@ describe('ReactHooks', () => {
1352 );
1353 expect(renderCount).toBe(__DEV__ ? 2 : 1);
1354
1354 - renderCount = 0;
1355 - expect(() => renderer.update(<Factory />)).toErrorDev(
1356 - 'Warning: The <Factory /> component appears to be a function component that returns a class instance. ' +
1357 - 'Change Factory to a class that extends React.Component instead. ' +
1358 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
1359 - '`Factory.prototype = React.Component.prototype`. ' +
1360 - "Don't use an arrow function since it cannot be called with `new` by React.",
1361 - );
1362 - expect(renderCount).toBe(1);
1363 - renderCount = 0;
1364 - renderer.update(<Factory />);
1365 - expect(renderCount).toBe(1);
1366 - renderCount = 0;
1367 - renderer.update(
1368 - <StrictMode>
1369 - <Factory />
1370 - </StrictMode>,
1371 - );
1372 - expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
1373 - renderCount = 0;
1374 - renderer.update(
1375 - <StrictMode>
1376 - <Factory />
1377 - </StrictMode>,
1378 - );
1379 - expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
1355 + if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
1356 + renderCount = 0;
1357 + expect(() => renderer.update(<Factory />)).toErrorDev(
1358 + 'Warning: The <Factory /> component appears to be a function component that returns a class instance. ' +
1359 + 'Change Factory to a class that extends React.Component instead. ' +
1360 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
1361 + '`Factory.prototype = React.Component.prototype`. ' +
1362 + "Don't use an arrow function since it cannot be called with `new` by React.",
1363 + );
1364 + expect(renderCount).toBe(1);
1365 + renderCount = 0;
1366 + renderer.update(<Factory />);
1367 + expect(renderCount).toBe(1);
1368 +
1369 + renderCount = 0;
1370 + renderer.update(
1371 + <StrictMode>
1372 + <Factory />
1373 + </StrictMode>,
1374 + );
1375 + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
1376 + renderCount = 0;
1377 + renderer.update(
1378 + <StrictMode>
1379 + <Factory />
1380 + </StrictMode>,
1381 + );
1382 + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
1383 + }
1384
1385 renderCount = 0;
1386 renderer.update(<HasHooks />);
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
+36 -34
@@ -187,41 +187,43 @@ describe('ReactHooksWithNoopRenderer', () => {
187 expect(Scheduler).toFlushAndYield([10]);
188 });
189
190 - it('throws inside module-style components', () => {
191 - function Counter() {
192 - return {
193 - render() {
194 - const [count] = useState(0);
195 - return <Text text={this.props.label + ': ' + count} />;
196 - },
197 - };
198 - }
199 - ReactNoop.render(<Counter />);
200 - expect(() =>
201 - expect(Scheduler).toFlushAndThrow(
202 - 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen ' +
203 - 'for one of the following reasons:\n' +
204 - '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
205 - '2. You might be breaking the Rules of Hooks\n' +
206 - '3. You might have more than one copy of React in the same app\n' +
207 - 'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
208 - ),
209 - ).toErrorDev(
210 - 'Warning: The <Counter /> component appears to be a function component that returns a class instance. ' +
211 - 'Change Counter to a class that extends React.Component instead. ' +
212 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
213 - '`Counter.prototype = React.Component.prototype`. ' +
214 - "Don't use an arrow function since it cannot be called with `new` by React.",
215 - );
190 + if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
191 + it('throws inside module-style components', () => {
192 + function Counter() {
193 + return {
194 + render() {
195 + const [count] = useState(0);
196 + return <Text text={this.props.label + ': ' + count} />;
197 + },
198 + };
199 + }
200 + ReactNoop.render(<Counter />);
201 + expect(() =>
202 + expect(Scheduler).toFlushAndThrow(
203 + 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen ' +
204 + 'for one of the following reasons:\n' +
205 + '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
206 + '2. You might be breaking the Rules of Hooks\n' +
207 + '3. You might have more than one copy of React in the same app\n' +
208 + 'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
209 + ),
210 + ).toErrorDev(
211 + 'Warning: The <Counter /> component appears to be a function component that returns a class instance. ' +
212 + 'Change Counter to a class that extends React.Component instead. ' +
213 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
214 + '`Counter.prototype = React.Component.prototype`. ' +
215 + "Don't use an arrow function since it cannot be called with `new` by React.",
216 + );
217
217 - // Confirm that a subsequent hook works properly.
218 - function GoodCounter(props) {
219 - const [count] = useState(props.initialCount);
220 - return <Text text={count} />;
221 - }
222 - ReactNoop.render(<GoodCounter initialCount={10} />);
223 - expect(Scheduler).toFlushAndYield([10]);
224 - });
218 + // Confirm that a subsequent hook works properly.
219 + function GoodCounter(props) {
220 + const [count] = useState(props.initialCount);
221 + return <Text text={count} />;
222 + }
223 + ReactNoop.render(<GoodCounter initialCount={10} />);
224 + expect(Scheduler).toFlushAndYield([10]);
225 + });
226 + }
227
228 it('throws when called outside the render phase', () => {
229 expect(() => useState(0)).toThrow(
packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js
+42 -40
@@ -2012,48 +2012,50 @@ describe('ReactIncremental', () => {
2012 ]);
2013 });
2014
2015 - it('does not leak own context into context provider (factory components)', () => {
2016 - const ops = [];
2017 - function Recurse(props, context) {
2018 - return {
2019 - getChildContext() {
2020 - return {n: (context.n || 3) - 1};
2021 - },
2022 - render() {
2023 - ops.push('Recurse ' + JSON.stringify(context));
2024 - if (context.n === 0) {
2025 - return null;
2026 - }
2027 - return <Recurse />;
2028 - },
2015 + if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
2016 + it('does not leak own context into context provider (factory components)', () => {
2017 + const ops = [];
2018 + function Recurse(props, context) {
2019 + return {
2020 + getChildContext() {
2021 + return {n: (context.n || 3) - 1};
2022 + },
2023 + render() {
2024 + ops.push('Recurse ' + JSON.stringify(context));
2025 + if (context.n === 0) {
2026 + return null;
2027 + }
2028 + return <Recurse />;
2029 + },
2030 + };
2031 + }
2032 + Recurse.contextTypes = {
2033 + n: PropTypes.number,
2034 + };
2035 + Recurse.childContextTypes = {
2036 + n: PropTypes.number,
2037 };
2030 - }
2031 - Recurse.contextTypes = {
2032 - n: PropTypes.number,
2033 - };
2034 - Recurse.childContextTypes = {
2035 - n: PropTypes.number,
2036 - };
2038
2038 - ReactNoop.render(<Recurse />);
2039 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev([
2040 - 'Warning: The <Recurse /> component appears to be a function component that returns a class instance. ' +
2041 - 'Change Recurse to a class that extends React.Component instead. ' +
2042 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
2043 - '`Recurse.prototype = React.Component.prototype`. ' +
2044 - "Don't use an arrow function since it cannot be called with `new` by React.",
2045 - 'Legacy context API has been detected within a strict-mode tree.\n\n' +
2046 - 'The old API will be supported in all 16.x releases, but applications ' +
2047 - 'using it should migrate to the new version.\n\n' +
2048 - 'Please update the following components: Recurse',
2049 - ]);
2050 - expect(ops).toEqual([
2051 - 'Recurse {}',
2052 - 'Recurse {"n":2}',
2053 - 'Recurse {"n":1}',
2054 - 'Recurse {"n":0}',
2055 - ]);
2056 - });
2039 + ReactNoop.render(<Recurse />);
2040 + expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev([
2041 + 'Warning: The <Recurse /> component appears to be a function component that returns a class instance. ' +
2042 + 'Change Recurse to a class that extends React.Component instead. ' +
2043 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
2044 + '`Recurse.prototype = React.Component.prototype`. ' +
2045 + "Don't use an arrow function since it cannot be called with `new` by React.",
2046 + 'Legacy context API has been detected within a strict-mode tree.\n\n' +
2047 + 'The old API will be supported in all 16.x releases, but applications ' +
2048 + 'using it should migrate to the new version.\n\n' +
2049 + 'Please update the following components: Recurse',
2050 + ]);
2051 + expect(ops).toEqual([
2052 + 'Recurse {}',
2053 + 'Recurse {"n":2}',
2054 + 'Recurse {"n":1}',
2055 + 'Recurse {"n":0}',
2056 + ]);
2057 + });
2058 + }
2059
2060 it('provides context when reusing work', () => {
2061 class Intl extends React.Component {
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+33 -31
@@ -1695,39 +1695,41 @@ describe('ReactIncrementalErrorHandling', () => {
1695 expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]);
1696 });
1697
1698 - it('handles error thrown inside getDerivedStateFromProps of a module-style context provider', () => {
1699 - function Provider() {
1700 - return {
1701 - getChildContext() {
1702 - return {foo: 'bar'};
1703 - },
1704 - render() {
1705 - return 'Hi';
1706 - },
1698 + if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
1699 + it('handles error thrown inside getDerivedStateFromProps of a module-style context provider', () => {
1700 + function Provider() {
1701 + return {
1702 + getChildContext() {
1703 + return {foo: 'bar'};
1704 + },
1705 + render() {
1706 + return 'Hi';
1707 + },
1708 + };
1709 + }
1710 + Provider.childContextTypes = {
1711 + x: () => {},
1712 + };
1713 + Provider.getDerivedStateFromProps = () => {
1714 + throw new Error('Oops!');
1715 };
1708 - }
1709 - Provider.childContextTypes = {
1710 - x: () => {},
1711 - };
1712 - Provider.getDerivedStateFromProps = () => {
1713 - throw new Error('Oops!');
1714 - };
1716
1716 - ReactNoop.render(<Provider />);
1717 - expect(() => {
1718 - expect(Scheduler).toFlushAndThrow('Oops!');
1719 - }).toErrorDev([
1720 - 'Warning: The <Provider /> component appears to be a function component that returns a class instance. ' +
1721 - 'Change Provider to a class that extends React.Component instead. ' +
1722 - "If you can't use a class try assigning the prototype on the function as a workaround. " +
1723 - '`Provider.prototype = React.Component.prototype`. ' +
1724 - "Don't use an arrow function since it cannot be called with `new` by React.",
1725 - 'Legacy context API has been detected within a strict-mode tree.\n\n' +
1726 - 'The old API will be supported in all 16.x releases, but ' +
1727 - 'applications using it should migrate to the new version.\n\n' +
1728 - 'Please update the following components: Provider',
1729 - ]);
1730 - });
1717 + ReactNoop.render(<Provider />);
1718 + expect(() => {
1719 + expect(Scheduler).toFlushAndThrow('Oops!');
1720 + }).toErrorDev([
1721 + 'Warning: The <Provider /> component appears to be a function component that returns a class instance. ' +
1722 + 'Change Provider to a class that extends React.Component instead. ' +
1723 + "If you can't use a class try assigning the prototype on the function as a workaround. " +
1724 + '`Provider.prototype = React.Component.prototype`. ' +
1725 + "Don't use an arrow function since it cannot be called with `new` by React.",
1726 + 'Legacy context API has been detected within a strict-mode tree.\n\n' +
1727 + 'The old API will be supported in all 16.x releases, but ' +
1728 + 'applications using it should migrate to the new version.\n\n' +
1729 + 'Please update the following components: Provider',
1730 + ]);
1731 + });
1732 + }
1733
1734 it('uncaught errors should be discarded if the render is aborted', async () => {
1735 const root = ReactNoop.createRoot();
packages/react-refresh/src/__tests__/ReactFreshIntegration-test.js
+35 -33
@@ -1379,51 +1379,53 @@ describe('ReactFreshIntegration', () => {
1379 }
1380 });
1381
1382 - it('remounts deprecated factory components', () => {
1383 - if (__DEV__) {
1384 - expect(() => {
1385 - render(`
1382 + if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
1383 + it('remounts deprecated factory components', () => {
1384 + if (__DEV__) {
1385 + expect(() => {
1386 + render(`
1387 + function Parent() {
1388 + return {
1389 + render() {
1390 + return <Child prop="A" />;
1391 + }
1392 + };
1393 + };
1394 +
1395 + function Child({prop}) {
1396 + return <h1>{prop}1</h1>;
1397 + };
1398 +
1399 + export default Parent;
1400 + `);
1401 + }).toErrorDev(
1402 + 'The <Parent /> component appears to be a function component ' +
1403 + 'that returns a class instance.',
1404 + );
1405 + const el = container.firstChild;
1406 + expect(el.textContent).toBe('A1');
1407 + patch(`
1408 function Parent() {
1409 return {
1410 render() {
1389 - return <Child prop="A" />;
1411 + return <Child prop="B" />;
1412 }
1413 };
1414 };
1415
1416 function Child({prop}) {
1395 - return <h1>{prop}1</h1>;
1417 + return <h1>{prop}2</h1>;
1418 };
1419
1420 export default Parent;
1421 `);
1400 - }).toErrorDev(
1401 - 'The <Parent /> component appears to be a function component ' +
1402 - 'that returns a class instance.',
1403 - );
1404 - const el = container.firstChild;
1405 - expect(el.textContent).toBe('A1');
1406 - patch(`
1407 - function Parent() {
1408 - return {
1409 - render() {
1410 - return <Child prop="B" />;
1411 - }
1412 - };
1413 - };
1414 -
1415 - function Child({prop}) {
1416 - return <h1>{prop}2</h1>;
1417 - };
1418 -
1419 - export default Parent;
1420 - `);
1421 - // Like classes, factory components always remount.
1422 - expect(container.firstChild).not.toBe(el);
1423 - const newEl = container.firstChild;
1424 - expect(newEl.textContent).toBe('B2');
1425 - }
1426 - });
1422 + // Like classes, factory components always remount.
1423 + expect(container.firstChild).not.toBe(el);
1424 + const newEl = container.firstChild;
1425 + expect(newEl.textContent).toBe('B2');
1426 + }
1427 + });
1428 + }
1429
1430 describe('with inline requires', () => {
1431 beforeEach(() => {
packages/shared/forks/ReactFeatureFlags.testing.www.js
+1 -1
@@ -36,7 +36,7 @@ export const disableLegacyContext = __EXPERIMENTAL__;
36 export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
37 export const enableTrustedTypesIntegration = false;
38 export const disableTextareaChildren = __EXPERIMENTAL__;
39 -export const disableModulePatternComponents = false;
39 +export const disableModulePatternComponents = true;
40 export const warnUnstableRenderSubtreeIntoContainer = false;
41 export const deferPassiveEffectCleanupDuringUnmount = true;
42 export const runAllPassiveEffectDestroysBeforeCreates = true;
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
@@ -14,6 +14,7 @@
14 // with the __VARIANT__ set to `true`, and once set to `false`.
15
16 export const warnAboutSpreadingKeyToJSX = __VARIANT__;
17 +export const disableModulePatternComponents = __VARIANT__;
18
19 // These are already tested in both modes using the build type dimension,
20 // so we don't need to use __VARIANT__ to get extra coverage.
packages/shared/forks/ReactFeatureFlags.www.js
+1 -2
@@ -16,6 +16,7 @@ const dynamicFeatureFlags: DynamicFeatureFlags = require('ReactFeatureFlags');
16
17 export const {
18 debugRenderPhaseSideEffectsForStrictMode,
19 + disableModulePatternComponents,
20 disableInputAttributeSyncing,
21 enableTrustedTypesIntegration,
22 warnAboutShorthandPropertyCollision,
@@ -65,8 +66,6 @@ export const flushSuspenseFallbacksInTests = true;
66
67 export const disableTextareaChildren = __EXPERIMENTAL__;
68
68 -export const disableModulePatternComponents = __EXPERIMENTAL__;
69 -
69 export const warnUnstableRenderSubtreeIntoContainer = false;
70
71 export const enableLegacyFBSupport = !__EXPERIMENTAL__;
scripts/jest/setupTests.www.js
-2
@@ -17,8 +17,6 @@ jest.mock('shared/ReactFeatureFlags', () => {
17 wwwFlags.warnAboutUnmockedScheduler = defaultFlags.warnAboutUnmockedScheduler;
18 wwwFlags.disableJavaScriptURLs = defaultFlags.disableJavaScriptURLs;
19 wwwFlags.enableDeprecatedFlareAPI = defaultFlags.enableDeprecatedFlareAPI;
20 - wwwFlags.disableModulePatternComponents =
21 - defaultFlags.disableModulePatternComponents;
20
21 return wwwFlags;
22 });