Deprecate module pattern (factory) components (#15145)

This commit is contained in:
Sebastian Markbåge
2019-03-19 12:55:27 -07:00
committed by GitHub
parent 55cc921c5d
commit acd65db5bc
14 changed files with 151 additions and 41 deletions
@@ -977,7 +977,16 @@ describe('ReactComponentLifeCycle', () => {
};
const div = document.createElement('div');
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
expect(() =>
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div),
).toWarnDev(
'Warning: The <Parent /> component appears to be a function component that returns a class instance. ' +
'Change Parent to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
'`Parent.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
{withoutStack: true},
);
ReactDOM.render(<Parent ref={c => c && log.push('ref')} />, div);
expect(log).toEqual([
@@ -118,7 +118,14 @@ describe('ReactCompositeComponent', () => {
}
const el = document.createElement('div');
ReactDOM.render(<Child test="test" />, el);
expect(() => ReactDOM.render(<Child test="test" />, el)).toWarnDev(
'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
'Change Child to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
'`Child.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
{withoutStack: true},
);
expect(el.textContent).toBe('test');
});
@@ -474,7 +474,14 @@ describe('ReactCompositeComponent-state', () => {
}
const el = document.createElement('div');
ReactDOM.render(<Child />, el);
expect(() => ReactDOM.render(<Child />, el)).toWarnDev(
'Warning: The <Child /> component appears to be a function component that returns a class instance. ' +
'Change Child to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
'`Child.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
{withoutStack: true},
);
expect(el.textContent).toBe('count:123');
});
@@ -644,7 +644,7 @@ describe('ReactDOMServerIntegration', () => {
},
};
};
checkFooDiv(await render(<FactoryComponent />));
checkFooDiv(await render(<FactoryComponent />, 1));
});
});
@@ -768,12 +768,23 @@ describe('ReactErrorBoundaries', () => {
};
const container = document.createElement('div');
ReactDOM.render(
<ErrorBoundary>
<BrokenComponentWillMountWithContext />
</ErrorBoundary>,
container,
expect(() =>
ReactDOM.render(
<ErrorBoundary>
<BrokenComponentWillMountWithContext />
</ErrorBoundary>,
container,
),
).toWarnDev(
'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
'returns a class instance. ' +
'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
'`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
{withoutStack: true},
);
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
});
@@ -742,11 +742,21 @@ describe('ReactLegacyErrorBoundaries', () => {
};
const container = document.createElement('div');
ReactDOM.render(
<ErrorBoundary>
<BrokenComponentWillMountWithContext />
</ErrorBoundary>,
container,
expect(() =>
ReactDOM.render(
<ErrorBoundary>
<BrokenComponentWillMountWithContext />
</ErrorBoundary>,
container,
),
).toWarnDev(
'Warning: The <BrokenComponentWillMountWithContext /> component appears to be a function component that ' +
'returns a class instance. ' +
'Change BrokenComponentWillMountWithContext to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
'`BrokenComponentWillMountWithContext.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
{withoutStack: true},
);
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
});
+11 -1
View File
@@ -166,7 +166,17 @@ describe('factory components', () => {
};
}
const inst = ReactTestUtils.renderIntoDocument(<Comp />);
let inst;
expect(
() => (inst = ReactTestUtils.renderIntoDocument(<Comp />)),
).toWarnDev(
'Warning: The <Comp /> component appears to be a function component that returns a class instance. ' +
'Change Comp to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
'`Comp.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
{withoutStack: true},
);
expect(inst.refs.elemRef.tagName).toBe('DIV');
});
});
+19
View File
@@ -179,6 +179,7 @@ let didWarnDefaultTextareaValue = false;
let didWarnInvalidOptionChildren = false;
const didWarnAboutNoopUpdateForComponent = {};
const didWarnAboutBadClass = {};
const didWarnAboutModulePatternComponent = {};
const didWarnAboutDeprecatedWillMount = {};
const didWarnAboutUndefinedDerivedState = {};
const didWarnAboutUninitializedState = {};
@@ -525,6 +526,24 @@ function resolve(
validateRenderResult(child, Component);
return;
}
if (__DEV__) {
const componentName = getComponentName(Component) || 'Unknown';
if (!didWarnAboutModulePatternComponent[componentName]) {
warningWithoutStack(
false,
'The <%s /> component appears to be a function component that returns a class instance. ' +
'Change %s to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
'cannot be called with `new` by React.',
componentName,
componentName,
componentName,
);
didWarnAboutModulePatternComponent[componentName] = true;
}
}
}
inst.props = element.props;
+20
View File
@@ -144,6 +144,7 @@ const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
let didReceiveUpdate: boolean = false;
let didWarnAboutBadClass;
let didWarnAboutModulePatternComponent;
let didWarnAboutContextTypeOnFunctionComponent;
let didWarnAboutGetDerivedStateOnFunctionComponent;
let didWarnAboutFunctionRefs;
@@ -151,6 +152,7 @@ export let didWarnAboutReassigningProps;
if (__DEV__) {
didWarnAboutBadClass = {};
didWarnAboutModulePatternComponent = {};
didWarnAboutContextTypeOnFunctionComponent = {};
didWarnAboutGetDerivedStateOnFunctionComponent = {};
didWarnAboutFunctionRefs = {};
@@ -1222,6 +1224,24 @@ function mountIndeterminateComponent(
typeof value.render === 'function' &&
value.$$typeof === undefined
) {
if (__DEV__) {
const componentName = getComponentName(Component) || 'Unknown';
if (!didWarnAboutModulePatternComponent[componentName]) {
warningWithoutStack(
false,
'The <%s /> component appears to be a function component that returns a class instance. ' +
'Change %s to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
"`%s.prototype = React.Component.prototype`. Don't use an arrow function since it " +
'cannot be called with `new` by React.',
componentName,
componentName,
componentName,
);
didWarnAboutModulePatternComponent[componentName] = true;
}
}
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;
@@ -1327,7 +1327,14 @@ describe('ReactHooks', () => {
expect(renderCount).toBe(1);
renderCount = 0;
renderer.update(<Factory />);
expect(() => renderer.update(<Factory />)).toWarnDev(
'Warning: The <Factory /> component appears to be a function component that returns a class instance. ' +
'Change Factory to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
'`Factory.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
{withoutStack: true},
);
expect(renderCount).toBe(1);
renderCount = 0;
renderer.update(<Factory />);
@@ -137,13 +137,22 @@ describe('ReactHooksWithNoopRenderer', () => {
};
}
ReactNoop.render(<Counter />);
expect(Scheduler).toFlushAndThrow(
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
' one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
expect(() =>
expect(Scheduler).toFlushAndThrow(
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen ' +
'for one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
),
).toWarnDev(
'Warning: The <Counter /> component appears to be a function component that returns a class instance. ' +
'Change Counter to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
'`Counter.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
{withoutStack: true},
);
// Confirm that a subsequent hook works properly.
@@ -2026,8 +2026,15 @@ describe('ReactIncremental', () => {
ReactNoop.render(<Recurse />);
expect(() => expect(Scheduler).toFlushWithoutYielding()).toWarnDev(
'Legacy context API has been detected within a strict-mode tree: \n\n' +
'Please update the following components: Recurse',
[
'Warning: The <Recurse /> component appears to be a function component that returns a class instance. ' +
'Change Recurse to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
'`Recurse.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
'Legacy context API has been detected within a strict-mode tree: \n\n' +
'Please update the following components: Recurse',
],
{withoutStack: true},
);
expect(ops).toEqual([
@@ -1596,8 +1596,15 @@ describe('ReactIncrementalErrorHandling', () => {
expect(() => {
expect(Scheduler).toFlushAndThrow('Oops!');
}).toWarnDev(
'Legacy context API has been detected within a strict-mode tree: \n\n' +
'Please update the following components: Provider',
[
'Warning: The <Provider /> component appears to be a function component that returns a class instance. ' +
'Change Provider to a class that extends React.Component instead. ' +
"If you can't use a class try assigning the prototype on the function as a workaround. " +
'`Provider.prototype = React.Component.prototype`. ' +
"Don't use an arrow function since it cannot be called with `new` by React.",
'Legacy context API has been detected within a strict-mode tree: \n\n' +
'Please update the following components: Provider',
],
{withoutStack: true},
);
});
@@ -835,7 +835,6 @@ describe('ReactStrictMode', () => {
<div>
<LegacyContextConsumer />
<FunctionalLegacyContextConsumer />
<FactoryLegacyContextConsumer />
</div>
);
}
@@ -845,14 +844,6 @@ describe('ReactStrictMode', () => {
return null;
}
function FactoryLegacyContextConsumer() {
return {
render() {
return null;
},
};
}
LegacyContextProvider.childContextTypes = {
color: PropTypes.string,
};
@@ -885,10 +876,6 @@ describe('ReactStrictMode', () => {
color: PropTypes.string,
};
FactoryLegacyContextConsumer.contextTypes = {
color: PropTypes.string,
};
let rendered;
expect(() => {
@@ -898,7 +885,7 @@ describe('ReactStrictMode', () => {
'\n in StrictMode (at **)' +
'\n in div (at **)' +
'\n in Root (at **)' +
'\n\nPlease update the following components: FactoryLegacyContextConsumer, ' +
'\n\nPlease update the following components: ' +
'FunctionalLegacyContextConsumer, LegacyContextConsumer, LegacyContextProvider' +
'\n\nLearn more about this warning here:' +
'\nhttps://fb.me/react-strict-mode-warnings',