Deprecate module pattern (factory) components (#15145)
Sebastian Markbåge committed
Mar 19, 2019 at 12:55 UTC
acd65db5bc2cd1ab19be66318f3e931054796739
14 files changed
+151
-41
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+10
-1
@@ -977,7 +977,16 @@ describe('ReactComponentLifeCycle', () => {
977
};
978
979
const div = document.createElement('div');
980
- ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
980
+ expect(() =>
981
+ ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div),
982
+ ).toWarnDev(
983
+ 'Warning: The <Parent /> component appears to be a function component that returns a class instance. ' +
984
+ 'Change Parent to a class that extends React.Component instead. ' +
985
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
986
+ '`Parent.prototype = React.Component.prototype`. ' +
987
+ "Don't use an arrow function since it cannot be called with `new` by React.",
988
+ {withoutStack: true},
989
+ );
990
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
991
992
expect(log).toEqual([
packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
+8
-1
@@ -118,7 +118,14 @@ describe('ReactCompositeComponent', () => {
118
}
119
120
const el = document.createElement('div');
121
- ReactDOM.render(<Child test="test" />, el);
121
+ expect(() => ReactDOM.render(<Child test="test" />, el)).toWarnDev(
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
+ {withoutStack: true},
128
+ );
129
130
expect(el.textContent).toBe('test');
131
});
packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js
+8
-1
@@ -474,7 +474,14 @@ describe('ReactCompositeComponent-state', () => {
474
}
475
476
const el = document.createElement('div');
477
- ReactDOM.render(<Child />, el);
477
+ expect(() => ReactDOM.render(<Child />, el)).toWarnDev(
478
+ 'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
479
+ 'Change Child to a class that extends React.Component instead. ' +
480
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
481
+ '`Child.prototype = React.Component.prototype`. ' +
482
+ "Don't use an arrow function since it cannot be called with `new` by React.",
483
+ {withoutStack: true},
484
+ );
485
486
expect(el.textContent).toBe('count:123');
487
});
packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js
+1
-1
@@ -644,7 +644,7 @@ describe('ReactDOMServerIntegration', () => {
644
},
645
};
646
};
647
- checkFooDiv(await render(<FactoryComponent />));
647
+ checkFooDiv(await render(<FactoryComponent />, 1));
648
});
649
});
650
packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
+16
-5
@@ -768,12 +768,23 @@ describe('ReactErrorBoundaries', () => {
768
};
769
770
const container = document.createElement('div');
771
- ReactDOM.render(
772
- <ErrorBoundary>
773
- <BrokenComponentWillMountWithContext />
774
- </ErrorBoundary>,
775
- container,
771
+ expect(() =>
772
+ ReactDOM.render(
773
+ <ErrorBoundary>
774
+ <BrokenComponentWillMountWithContext />
775
+ </ErrorBoundary>,
776
+ container,
777
+ ),
778
+ ).toWarnDev(
779
+ 'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
780
+ 'returns a class instance. ' +
781
+ 'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
782
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
783
+ '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
784
+ "Don't use an arrow function since it cannot be called with `new` by React.",
785
+ {withoutStack: true},
786
);
787
+
788
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
789
});
790
packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js
+15
-5
@@ -742,11 +742,21 @@ describe('ReactLegacyErrorBoundaries', () => {
742
};
743
744
const container = document.createElement('div');
745
- ReactDOM.render(
746
- <ErrorBoundary>
747
- <BrokenComponentWillMountWithContext />
748
- </ErrorBoundary>,
749
- container,
745
+ expect(() =>
746
+ ReactDOM.render(
747
+ <ErrorBoundary>
748
+ <BrokenComponentWillMountWithContext />
749
+ </ErrorBoundary>,
750
+ container,
751
+ ),
752
+ ).toWarnDev(
753
+ 'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
754
+ 'returns a class instance. ' +
755
+ 'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
756
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
757
+ '`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
758
+ "Don't use an arrow function since it cannot be called with `new` by React.",
759
+ {withoutStack: true},
760
);
761
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
762
});
packages/react-dom/src/__tests__/refs-test.js
+11
-1
@@ -166,7 +166,17 @@ describe('factory components', () => {
166
};
167
}
168
169
- const inst = ReactTestUtils.renderIntoDocument(<Comp />);
169
+ let inst;
170
+ expect(
171
+ () => (inst = ReactTestUtils.renderIntoDocument(<Comp />)),
172
+ ).toWarnDev(
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
+ {withoutStack: true},
179
+ );
180
expect(inst.refs.elemRef.tagName).toBe('DIV');
181
});
182
});
packages/react-dom/src/server/ReactPartialRenderer.js
+19
@@ -179,6 +179,7 @@ let didWarnDefaultTextareaValue = false;
179
let didWarnInvalidOptionChildren = false;
180
const didWarnAboutNoopUpdateForComponent = {};
181
const didWarnAboutBadClass = {};
182
+const didWarnAboutModulePatternComponent = {};
183
const didWarnAboutDeprecatedWillMount = {};
184
const didWarnAboutUndefinedDerivedState = {};
185
const didWarnAboutUninitializedState = {};
@@ -525,6 +526,24 @@ function resolve(
526
validateRenderResult(child, Component);
527
return;
528
}
529
+
530
+ if (__DEV__) {
531
+ const componentName = getComponentName(Component) || 'Unknown';
532
+ if (!didWarnAboutModulePatternComponent[componentName]) {
533
+ warningWithoutStack(
534
+ false,
535
+ 'The <%s /> component appears to be a function component that returns a class instance. ' +
536
+ 'Change %s to a class that extends React.Component instead. ' +
537
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
538
+ "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
539
+ 'cannot be called with `new` by React.',
540
+ componentName,
541
+ componentName,
542
+ componentName,
543
+ );
544
+ didWarnAboutModulePatternComponent[componentName] = true;
545
+ }
546
+ }
547
}
548
549
inst.props = element.props;
packages/react-reconciler/src/ReactFiberBeginWork.js
+20
@@ -144,6 +144,7 @@ const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
144
let didReceiveUpdate: boolean = false;
145
146
let didWarnAboutBadClass;
147
+let didWarnAboutModulePatternComponent;
148
let didWarnAboutContextTypeOnFunctionComponent;
149
let didWarnAboutGetDerivedStateOnFunctionComponent;
150
let didWarnAboutFunctionRefs;
@@ -151,6 +152,7 @@ export let didWarnAboutReassigningProps;
152
153
if (__DEV__) {
154
didWarnAboutBadClass = {};
155
+ didWarnAboutModulePatternComponent = {};
156
didWarnAboutContextTypeOnFunctionComponent = {};
157
didWarnAboutGetDerivedStateOnFunctionComponent = {};
158
didWarnAboutFunctionRefs = {};
@@ -1222,6 +1224,24 @@ function mountIndeterminateComponent(
1224
typeof value.render === 'function' &&
1225
value.$$typeof === undefined
1226
) {
1227
+ if (__DEV__) {
1228
+ const componentName = getComponentName(Component) || 'Unknown';
1229
+ if (!didWarnAboutModulePatternComponent[componentName]) {
1230
+ warningWithoutStack(
1231
+ false,
1232
+ 'The <%s /> component appears to be a function component that returns a class instance. ' +
1233
+ 'Change %s to a class that extends React.Component instead. ' +
1234
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
1235
+ "`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
1236
+ 'cannot be called with `new` by React.',
1237
+ componentName,
1238
+ componentName,
1239
+ componentName,
1240
+ );
1241
+ didWarnAboutModulePatternComponent[componentName] = true;
1242
+ }
1243
+ }
1244
+
1245
// Proceed under the assumption that this is a class instance
1246
workInProgress.tag = ClassComponent;
1247
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+8
-1
@@ -1327,7 +1327,14 @@ describe('ReactHooks', () => {
1327
expect(renderCount).toBe(1);
1328
1329
renderCount = 0;
1330
- renderer.update(<Factory />);
1330
+ expect(() => renderer.update(<Factory />)).toWarnDev(
1331
+ 'Warning: The <Factory /> component appears to be a function component that returns a class instance. ' +
1332
+ 'Change Factory to a class that extends React.Component instead. ' +
1333
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
1334
+ '`Factory.prototype = React.Component.prototype`. ' +
1335
+ "Don't use an arrow function since it cannot be called with `new` by React.",
1336
+ {withoutStack: true},
1337
+ );
1338
expect(renderCount).toBe(1);
1339
renderCount = 0;
1340
renderer.update(<Factory />);
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
+16
-7
@@ -137,13 +137,22 @@ describe('ReactHooksWithNoopRenderer', () => {
137
};
138
}
139
ReactNoop.render(<Counter />);
140
- expect(Scheduler).toFlushAndThrow(
141
- 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
142
- ' one of the following reasons:\n' +
143
- '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
144
- '2. You might be breaking the Rules of Hooks\n' +
145
- '3. You might have more than one copy of React in the same app\n' +
146
- 'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
140
+ expect(() =>
141
+ expect(Scheduler).toFlushAndThrow(
142
+ 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen ' +
143
+ 'for one of the following reasons:\n' +
144
+ '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
145
+ '2. You might be breaking the Rules of Hooks\n' +
146
+ '3. You might have more than one copy of React in the same app\n' +
147
+ 'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
148
+ ),
149
+ ).toWarnDev(
150
+ 'Warning: The <Counter /> component appears to be a function component that returns a class instance. ' +
151
+ 'Change Counter to a class that extends React.Component instead. ' +
152
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
153
+ '`Counter.prototype = React.Component.prototype`. ' +
154
+ "Don't use an arrow function since it cannot be called with `new` by React.",
155
+ {withoutStack: true},
156
);
157
158
// Confirm that a subsequent hook works properly.
packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js
+9
-2
@@ -2026,8 +2026,15 @@ describe('ReactIncremental', () => {
2026
2027
ReactNoop.render(<Recurse />);
2028
expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev(
2029
- 'Legacy context API has been detected within a strict-mode tree: \n\n' +
2030
- 'Please update the following components: Recurse',
2029
+ [
2030
+ 'Warning: The <Recurse /> component appears to be a function component that returns a class instance. ' +
2031
+ 'Change Recurse to a class that extends React.Component instead. ' +
2032
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
2033
+ '`Recurse.prototype = React.Component.prototype`. ' +
2034
+ "Don't use an arrow function since it cannot be called with `new` by React.",
2035
+ 'Legacy context API has been detected within a strict-mode tree: \n\n' +
2036
+ 'Please update the following components: Recurse',
2037
+ ],
2038
{withoutStack: true},
2039
);
2040
expect(ops).toEqual([
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+9
-2
@@ -1596,8 +1596,15 @@ describe('ReactIncrementalErrorHandling', () => {
1596
expect(() => {
1597
expect(Scheduler).toFlushAndThrow('Oops!');
1598
}).toWarnDev(
1599
- 'Legacy context API has been detected within a strict-mode tree: \n\n' +
1600
- 'Please update the following components: Provider',
1599
+ [
1600
+ 'Warning: The <Provider /> component appears to be a function component that returns a class instance. ' +
1601
+ 'Change Provider to a class that extends React.Component instead. ' +
1602
+ "If you can't use a class try assigning the prototype on the function as a workaround. " +
1603
+ '`Provider.prototype = React.Component.prototype`. ' +
1604
+ "Don't use an arrow function since it cannot be called with `new` by React.",
1605
+ 'Legacy context API has been detected within a strict-mode tree: \n\n' +
1606
+ 'Please update the following components: Provider',
1607
+ ],
1608
{withoutStack: true},
1609
);
1610
});
packages/react/src/__tests__/ReactStrictMode-test.internal.js
+1
-14
@@ -835,7 +835,6 @@ describe('ReactStrictMode', () => {
835
<div>
836
<LegacyContextConsumer />
837
<FunctionalLegacyContextConsumer />
838
- <FactoryLegacyContextConsumer />
838
</div>
839
);
840
}
@@ -845,14 +844,6 @@ describe('ReactStrictMode', () => {
844
return null;
845
}
846
848
- function FactoryLegacyContextConsumer() {
849
- return {
850
- render() {
851
- return null;
852
- },
853
- };
854
- }
855
-
847
LegacyContextProvider.childContextTypes = {
848
color: PropTypes.string,
849
};
@@ -885,10 +876,6 @@ describe('ReactStrictMode', () => {
876
color: PropTypes.string,
877
};
878
888
- FactoryLegacyContextConsumer.contextTypes = {
889
- color: PropTypes.string,
890
- };
891
-
879
let rendered;
880
881
expect(() => {
@@ -898,7 +885,7 @@ describe('ReactStrictMode', () => {
885
'\n in StrictMode (at **)' +
886
'\n in div (at **)' +
887
'\n in Root (at **)' +
901
- '\n\nPlease update the following components: FactoryLegacyContextConsumer, ' +
888
+ '\n\nPlease update the following components: ' +
889
'FunctionalLegacyContextConsumer, LegacyContextConsumer, LegacyContextProvider' +
890
'\n\nLearn more about this warning here:' +
891
'\nhttps://fb.me/react-strict-mode-warnings',