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
});