diff --git a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js
index aa7fbf73c1a02..aa9be8e8d4d56 100644
--- a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js
@@ -24,6 +24,10 @@ describe('ReactComponentLifeCycle', () => {
ReactDOM = require('react-dom');
});
+ afterEach(() => {
+ jest.resetModules();
+ });
+
// TODO (RFC #6) Merge this back into ReactComponentLifeCycles-test once
// the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
it('warns about deprecated unsafe lifecycles', function() {
@@ -55,4 +59,55 @@ describe('ReactComponentLifeCycle', () => {
ReactDOM.render(, container);
ReactDOM.render(, container);
});
+
+ describe('react-lifecycles-compat', () => {
+ // TODO Replace this with react-lifecycles-compat once it's been published
+ function polyfill(Component) {
+ Component.prototype.componentWillMount = function() {};
+ Component.prototype.componentWillMount.__suppressDeprecationWarning = true;
+ Component.prototype.componentWillReceiveProps = function() {};
+ Component.prototype.componentWillReceiveProps.__suppressDeprecationWarning = true;
+ }
+
+ it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
+ class PolyfilledComponent extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ render() {
+ return null;
+ }
+ }
+
+ polyfill(PolyfilledComponent);
+
+ const container = document.createElement('div');
+ ReactDOM.render(, container);
+ });
+
+ it('should not warn about unsafe lifecycles within "strict" tree for polyfilled components', () => {
+ const {StrictMode} = React;
+
+ class PolyfilledComponent extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ render() {
+ return null;
+ }
+ }
+
+ polyfill(PolyfilledComponent);
+
+ const container = document.createElement('div');
+ ReactDOM.render(
+
+
+ ,
+ container,
+ );
+ });
+ });
});
diff --git a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
index 81591d82fb0ef..b8f53000496ab 100644
--- a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+++ b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
@@ -499,7 +499,7 @@ describe('ReactComponentLifeCycle', () => {
expect(instance.state.stateField).toBe('goodbye');
});
- it('should call nested lifecycle methods in the right order', () => {
+ it('should call nested legacy lifecycle methods in the right order', () => {
let log;
const logger = function(msg) {
return function() {
@@ -509,11 +509,6 @@ describe('ReactComponentLifeCycle', () => {
};
};
class Outer extends React.Component {
- state = {};
- static getDerivedStateFromProps(props, prevState) {
- log.push('outer getDerivedStateFromProps');
- return null;
- }
UNSAFE_componentWillMount = logger('outer componentWillMount');
componentDidMount = logger('outer componentDidMount');
UNSAFE_componentWillReceiveProps = logger(
@@ -533,11 +528,6 @@ describe('ReactComponentLifeCycle', () => {
}
class Inner extends React.Component {
- state = {};
- static getDerivedStateFromProps(props, prevState) {
- log.push('inner getDerivedStateFromProps');
- return null;
- }
UNSAFE_componentWillMount = logger('inner componentWillMount');
componentDidMount = logger('inner componentDidMount');
UNSAFE_componentWillReceiveProps = logger(
@@ -554,18 +544,9 @@ describe('ReactComponentLifeCycle', () => {
const container = document.createElement('div');
log = [];
- expect(() => ReactDOM.render(, container)).toWarnDev([
- 'Warning: Outer: Defines both componentWillReceiveProps() and static ' +
- 'getDerivedStateFromProps() methods. ' +
- 'We recommend using only getDerivedStateFromProps().',
- 'Warning: Inner: Defines both componentWillReceiveProps() and static ' +
- 'getDerivedStateFromProps() methods. ' +
- 'We recommend using only getDerivedStateFromProps().',
- ]);
+ ReactDOM.render(, container);
expect(log).toEqual([
- 'outer getDerivedStateFromProps',
'outer componentWillMount',
- 'inner getDerivedStateFromProps',
'inner componentWillMount',
'inner componentDidMount',
'outer componentDidMount',
@@ -576,11 +557,9 @@ describe('ReactComponentLifeCycle', () => {
ReactDOM.render(, container);
expect(log).toEqual([
'outer componentWillReceiveProps',
- 'outer getDerivedStateFromProps',
'outer shouldComponentUpdate',
'outer componentWillUpdate',
'inner componentWillReceiveProps',
- 'inner getDerivedStateFromProps',
'inner shouldComponentUpdate',
'inner componentWillUpdate',
'inner componentDidUpdate',
@@ -595,6 +574,131 @@ describe('ReactComponentLifeCycle', () => {
]);
});
+ it('should call nested new lifecycle methods in the right order', () => {
+ let log;
+ const logger = function(msg) {
+ return function() {
+ // return true for shouldComponentUpdate
+ log.push(msg);
+ return true;
+ };
+ };
+ class Outer extends React.Component {
+ state = {};
+ static getDerivedStateFromProps(props, prevState) {
+ log.push('outer getDerivedStateFromProps');
+ return null;
+ }
+ componentDidMount = logger('outer componentDidMount');
+ shouldComponentUpdate = logger('outer shouldComponentUpdate');
+ componentDidUpdate = logger('outer componentDidUpdate');
+ componentWillUnmount = logger('outer componentWillUnmount');
+ render() {
+ return (
+
+
+
+ );
+ }
+ }
+
+ class Inner extends React.Component {
+ state = {};
+ static getDerivedStateFromProps(props, prevState) {
+ log.push('inner getDerivedStateFromProps');
+ return null;
+ }
+ componentDidMount = logger('inner componentDidMount');
+ shouldComponentUpdate = logger('inner shouldComponentUpdate');
+ componentDidUpdate = logger('inner componentDidUpdate');
+ componentWillUnmount = logger('inner componentWillUnmount');
+ render() {
+ return {this.props.x};
+ }
+ }
+
+ const container = document.createElement('div');
+ log = [];
+ ReactDOM.render(, container);
+ expect(log).toEqual([
+ 'outer getDerivedStateFromProps',
+ 'inner getDerivedStateFromProps',
+ 'inner componentDidMount',
+ 'outer componentDidMount',
+ ]);
+
+ // Dedup warnings
+ log = [];
+ ReactDOM.render(, container);
+ expect(log).toEqual([
+ 'outer getDerivedStateFromProps',
+ 'outer shouldComponentUpdate',
+ 'inner getDerivedStateFromProps',
+ 'inner shouldComponentUpdate',
+ 'inner componentDidUpdate',
+ 'outer componentDidUpdate',
+ ]);
+
+ log = [];
+ ReactDOM.unmountComponentAtNode(container);
+ expect(log).toEqual([
+ 'outer componentWillUnmount',
+ 'inner componentWillUnmount',
+ ]);
+ });
+
+ it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
+ class Component extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ componentWillMount() {
+ throw Error('unexpected');
+ }
+ componentWillReceiveProps() {
+ throw Error('unexpected');
+ }
+ componentWillUpdate() {
+ throw Error('unexpected');
+ }
+ render() {
+ return null;
+ }
+ }
+
+ const container = document.createElement('div');
+ expect(() => ReactDOM.render(, container)).toWarnDev(
+ 'Defines both componentWillReceiveProps',
+ );
+ });
+
+ it('should not invoke new unsafe lifecycles (cWM/cWRP/cWU) if static gDSFP is present', () => {
+ class Component extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ UNSAFE_componentWillMount() {
+ throw Error('unexpected');
+ }
+ UNSAFE_componentWillReceiveProps() {
+ throw Error('unexpected');
+ }
+ UNSAFE_componentWillUpdate() {
+ throw Error('unexpected');
+ }
+ render() {
+ return null;
+ }
+ }
+
+ const container = document.createElement('div');
+ expect(() => ReactDOM.render(, container)).toWarnDev(
+ 'Defines both componentWillReceiveProps',
+ );
+ });
+
it('calls effects on module-pattern component', function() {
const log = [];
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.internal.js
index 5dd25cc4f718b..5f6e9e10c1ddb 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.internal.js
@@ -22,6 +22,29 @@ describe('ReactDOMServerLifecycles', () => {
ReactDOMServer = require('react-dom/server');
});
+ afterEach(() => {
+ jest.resetModules();
+ });
+
+ it('should not invoke cWM if static gDSFP is present', () => {
+ class Component extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ componentWillMount() {
+ throw Error('unexpected');
+ }
+ render() {
+ return null;
+ }
+ }
+
+ expect(() => ReactDOMServer.renderToString()).toWarnDev(
+ 'Component: componentWillMount() is deprecated and will be removed in the next major version.',
+ );
+ });
+
// TODO (RFC #6) Merge this back into ReactDOMServerLifecycles-test once
// the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
it('should warn about deprecated lifecycle hooks', () => {
@@ -40,4 +63,30 @@ describe('ReactDOMServerLifecycles', () => {
// De-duped
ReactDOMServer.renderToString();
});
+
+ describe('react-lifecycles-compat', () => {
+ // TODO Replace this with react-lifecycles-compat once it's been published
+ function polyfill(Component) {
+ Component.prototype.componentWillMount = function() {};
+ Component.prototype.componentWillMount.__suppressDeprecationWarning = true;
+ Component.prototype.componentWillReceiveProps = function() {};
+ Component.prototype.componentWillReceiveProps.__suppressDeprecationWarning = true;
+ }
+
+ it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
+ class PolyfilledComponent extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ render() {
+ return null;
+ }
+ }
+
+ polyfill(PolyfilledComponent);
+
+ ReactDOMServer.renderToString();
+ });
+ });
});
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js b/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js
index 3de800d0ace3c..10171a0e068e3 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js
@@ -33,7 +33,7 @@ describe('ReactDOMServerLifecycles', () => {
resetModules();
});
- it('should invoke the correct lifecycle hooks', () => {
+ it('should invoke the correct legacy lifecycle hooks', () => {
const log = [];
class Outer extends React.Component {
@@ -65,6 +65,59 @@ describe('ReactDOMServerLifecycles', () => {
]);
});
+ it('should invoke the correct new lifecycle hooks', () => {
+ const log = [];
+
+ class Outer extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ log.push('outer getDerivedStateFromProps');
+ return null;
+ }
+ render() {
+ log.push('outer render');
+ return ;
+ }
+ }
+
+ class Inner extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ log.push('inner getDerivedStateFromProps');
+ return null;
+ }
+ render() {
+ log.push('inner render');
+ return null;
+ }
+ }
+
+ ReactDOMServer.renderToString();
+ expect(log).toEqual([
+ 'outer getDerivedStateFromProps',
+ 'outer render',
+ 'inner getDerivedStateFromProps',
+ 'inner render',
+ ]);
+ });
+
+ it('should not invoke unsafe cWM if static gDSFP is present', () => {
+ class Component extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ UNSAFE_componentWillMount() {
+ throw Error('unexpected');
+ }
+ render() {
+ return null;
+ }
+ }
+
+ ReactDOMServer.renderToString();
+ });
+
it('should update instance.state with value returned from getDerivedStateFromProps', () => {
class Grandparent extends React.Component {
state = {
diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js
index 4e6622cac1630..99d409cd12cde 100644
--- a/packages/react-dom/src/server/ReactPartialRenderer.js
+++ b/packages/react-dom/src/server/ReactPartialRenderer.js
@@ -514,7 +514,10 @@ function resolve(
if (inst.UNSAFE_componentWillMount || inst.componentWillMount) {
if (inst.componentWillMount) {
if (__DEV__) {
- if (warnAboutDeprecatedLifecycles) {
+ if (
+ warnAboutDeprecatedLifecycles &&
+ inst.componentWillMount.__suppressDeprecationWarning !== true
+ ) {
const componentName = getComponentName(Component) || 'Unknown';
if (!didWarnAboutDeprecatedWillMount[componentName]) {
@@ -534,8 +537,14 @@ function resolve(
}
}
- inst.componentWillMount();
- } else {
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
+ if (typeof Component.getDerivedStateFromProps !== 'function') {
+ inst.componentWillMount();
+ }
+ } else if (typeof Component.getDerivedStateFromProps !== 'function') {
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
inst.UNSAFE_componentWillMount();
}
if (queue.length) {
diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js
index 90ca86a992dad..7f83d38d0532a 100644
--- a/packages/react-reconciler/src/ReactFiberClassComponent.js
+++ b/packages/react-reconciler/src/ReactFiberClassComponent.js
@@ -524,8 +524,11 @@ export default function(
if (typeof type.getDerivedStateFromProps === 'function') {
if (__DEV__) {
+ // Don't warn about react-lifecycles-compat polyfilled components
if (
- typeof instance.componentWillReceiveProps === 'function' ||
+ (typeof instance.componentWillReceiveProps === 'function' &&
+ instance.componentWillReceiveProps.__suppressDeprecationWarning !==
+ true) ||
typeof instance.UNSAFE_componentWillReceiveProps === 'function'
) {
const componentName = getComponentName(workInProgress) || 'Unknown';
@@ -627,9 +630,12 @@ export default function(
}
}
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
if (
- typeof instance.UNSAFE_componentWillMount === 'function' ||
- typeof instance.componentWillMount === 'function'
+ (typeof instance.UNSAFE_componentWillMount === 'function' ||
+ typeof instance.componentWillMount === 'function') &&
+ typeof workInProgress.type.getDerivedStateFromProps !== 'function'
) {
callComponentWillMount(workInProgress, instance);
// If we had additional state updates during this life-cycle, let's
@@ -775,17 +781,21 @@ export default function(
// ever the previously attempted to render - not the "current". However,
// during componentDidUpdate we pass the "current" props.
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
if (
(typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
typeof instance.componentWillReceiveProps === 'function') &&
- (oldProps !== newProps || oldContext !== newContext)
+ typeof workInProgress.type.getDerivedStateFromProps !== 'function'
) {
- callComponentWillReceiveProps(
- workInProgress,
- instance,
- newProps,
- newContext,
- );
+ if (oldProps !== newProps || oldContext !== newContext) {
+ callComponentWillReceiveProps(
+ workInProgress,
+ instance,
+ newProps,
+ newContext,
+ );
+ }
}
let partialState;
@@ -856,9 +866,12 @@ export default function(
);
if (shouldUpdate) {
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
if (
- typeof instance.UNSAFE_componentWillUpdate === 'function' ||
- typeof instance.componentWillUpdate === 'function'
+ (typeof instance.UNSAFE_componentWillUpdate === 'function' ||
+ typeof instance.componentWillUpdate === 'function') &&
+ typeof workInProgress.type.getDerivedStateFromProps !== 'function'
) {
if (typeof instance.componentWillUpdate === 'function') {
startPhaseTimer(workInProgress, 'componentWillUpdate');
diff --git a/packages/react-reconciler/src/ReactStrictModeWarnings.js b/packages/react-reconciler/src/ReactStrictModeWarnings.js
index fb1b15d3d1618..8be9b1dc50936 100644
--- a/packages/react-reconciler/src/ReactStrictModeWarnings.js
+++ b/packages/react-reconciler/src/ReactStrictModeWarnings.js
@@ -199,6 +199,16 @@ if (__DEV__) {
return;
}
+ // Don't warn about react-lifecycles-compat polyfilled components.
+ // Note that it is sufficient to check for the presence of a
+ // single lifecycle, componentWillMount, with the polyfill flag.
+ if (
+ typeof instance.componentWillMount === 'function' &&
+ instance.componentWillMount.__suppressDeprecationWarning === true
+ ) {
+ return;
+ }
+
if (typeof instance.componentWillMount === 'function') {
pendingComponentWillMountWarnings.push(fiber);
}
@@ -225,6 +235,16 @@ if (__DEV__) {
return;
}
+ // Don't warn about react-lifecycles-compat polyfilled components.
+ // Note that it is sufficient to check for the presence of a
+ // single lifecycle, componentWillMount, with the polyfill flag.
+ if (
+ typeof instance.componentWillMount === 'function' &&
+ instance.componentWillMount.__suppressDeprecationWarning === true
+ ) {
+ return;
+ }
+
let warningsForRoot;
if (!pendingUnsafeLifecycleWarnings.has(strictRoot)) {
warningsForRoot = {
diff --git a/packages/react-test-renderer/src/ReactShallowRenderer.js b/packages/react-test-renderer/src/ReactShallowRenderer.js
index fc2ffbc8551ca..01fd15d28c908 100644
--- a/packages/react-test-renderer/src/ReactShallowRenderer.js
+++ b/packages/react-test-renderer/src/ReactShallowRenderer.js
@@ -180,7 +180,12 @@ class ReactShallowRenderer {
if (typeof this._instance.componentWillMount === 'function') {
if (__DEV__) {
- if (warnAboutDeprecatedLifecycles) {
+ // Don't warn about react-lifecycles-compat polyfilled components
+ if (
+ warnAboutDeprecatedLifecycles &&
+ this._instance.componentWillMount.__suppressDeprecationWarning !==
+ true
+ ) {
const componentName = getName(element.type, this._instance);
if (!didWarnAboutLegacyWillMount[componentName]) {
warning(
@@ -198,8 +203,15 @@ class ReactShallowRenderer {
}
}
}
- this._instance.componentWillMount();
- } else {
+
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
+ if (typeof element.type.getDerivedStateFromProps !== 'function') {
+ this._instance.componentWillMount();
+ }
+ } else if (typeof element.type.getDerivedStateFromProps !== 'function') {
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
this._instance.UNSAFE_componentWillMount();
}
@@ -242,10 +254,17 @@ class ReactShallowRenderer {
}
}
}
- this._instance.componentWillReceiveProps(props, context);
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
+ if (typeof element.type.getDerivedStateFromProps !== 'function') {
+ this._instance.componentWillReceiveProps(props, context);
+ }
} else if (
- typeof this._instance.UNSAFE_componentWillReceiveProps === 'function'
+ typeof this._instance.UNSAFE_componentWillReceiveProps === 'function' &&
+ typeof element.type.getDerivedStateFromProps !== 'function'
) {
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
this._instance.UNSAFE_componentWillReceiveProps(props, context);
}
@@ -292,10 +311,17 @@ class ReactShallowRenderer {
}
}
- this._instance.componentWillUpdate(props, state, context);
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
+ if (typeof type.getDerivedStateFromProps !== 'function') {
+ this._instance.componentWillUpdate(props, state, context);
+ }
} else if (
- typeof this._instance.UNSAFE_componentWillUpdate === 'function'
+ typeof this._instance.UNSAFE_componentWillUpdate === 'function' &&
+ typeof type.getDerivedStateFromProps !== 'function'
) {
+ // In order to support react-lifecycles-compat polyfilled components,
+ // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
this._instance.UNSAFE_componentWillUpdate(props, state, context);
}
}
@@ -316,8 +342,11 @@ class ReactShallowRenderer {
if (typeof type.getDerivedStateFromProps === 'function') {
if (__DEV__) {
+ // Don't warn about react-lifecycles-compat polyfilled components
if (
- typeof this._instance.componentWillReceiveProps === 'function' ||
+ (typeof this._instance.componentWillReceiveProps === 'function' &&
+ this._instance.componentWillReceiveProps
+ .__suppressDeprecationWarning !== true) ||
typeof this._instance.UNSAFE_componentWillReceiveProps === 'function'
) {
const componentName = getName(type, this._instance);
diff --git a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.internal.js b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.internal.js
index 0c5813f7b2cfe..0d1957a9806e6 100644
--- a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.internal.js
+++ b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.internal.js
@@ -23,6 +23,10 @@ describe('ReactShallowRenderer', () => {
React = require('react');
});
+ afterEach(() => {
+ jest.resetModules();
+ });
+
// TODO (RFC #6) Merge this back into ReactShallowRenderer-test once
// the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
it('should warn if deprecated lifecycles exist', () => {
@@ -50,4 +54,31 @@ describe('ReactShallowRenderer', () => {
// Verify no duplicate warnings
shallowRenderer.render();
});
+
+ describe('react-lifecycles-compat', () => {
+ // TODO Replace this with react-lifecycles-compat once it's been published
+ function polyfill(Component) {
+ Component.prototype.componentWillMount = function() {};
+ Component.prototype.componentWillMount.__suppressDeprecationWarning = true;
+ Component.prototype.componentWillReceiveProps = function() {};
+ Component.prototype.componentWillReceiveProps.__suppressDeprecationWarning = true;
+ }
+
+ it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
+ class PolyfilledComponent extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ render() {
+ return null;
+ }
+ }
+
+ polyfill(PolyfilledComponent);
+
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render();
+ });
+ });
});
diff --git a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
index 02532ec6fb799..240129421efa6 100644
--- a/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
+++ b/packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
@@ -23,13 +23,11 @@ describe('ReactShallowRenderer', () => {
React = require('react');
});
- it('should call all of the lifecycle hooks', () => {
+ it('should call all of the legacy lifecycle hooks', () => {
const logs = [];
const logger = message => () => logs.push(message) || true;
class SomeComponent extends React.Component {
- state = {};
- static getDerivedStateFromProps = logger('getDerivedStateFromProps');
UNSAFE_componentWillMount = logger('componentWillMount');
componentDidMount = logger('componentDidMount');
UNSAFE_componentWillReceiveProps = logger('componentWillReceiveProps');
@@ -43,16 +41,11 @@ describe('ReactShallowRenderer', () => {
}
const shallowRenderer = createRenderer();
-
- expect(() => shallowRenderer.render()).toWarnDev(
- 'Warning: SomeComponent: Defines both componentWillReceiveProps() and static ' +
- 'getDerivedStateFromProps() methods. ' +
- 'We recommend using only getDerivedStateFromProps().',
- );
+ shallowRenderer.render();
// Calling cDU might lead to problems with host component references.
// Since our components aren't really mounted, refs won't be available.
- expect(logs).toEqual(['getDerivedStateFromProps', 'componentWillMount']);
+ expect(logs).toEqual(['componentWillMount']);
logs.splice(0);
@@ -68,12 +61,75 @@ describe('ReactShallowRenderer', () => {
// The previous shallow renderer did not trigger cDU for props changes.
expect(logs).toEqual([
'componentWillReceiveProps',
- 'getDerivedStateFromProps',
'shouldComponentUpdate',
'componentWillUpdate',
]);
});
+ it('should call all of the new lifecycle hooks', () => {
+ const logs = [];
+ const logger = message => () => logs.push(message) || true;
+
+ class SomeComponent extends React.Component {
+ state = {};
+ static getDerivedStateFromProps = logger('getDerivedStateFromProps');
+ componentDidMount = logger('componentDidMount');
+ shouldComponentUpdate = logger('shouldComponentUpdate');
+ componentDidUpdate = logger('componentDidUpdate');
+ componentWillUnmount = logger('componentWillUnmount');
+ render() {
+ return ;
+ }
+ }
+
+ const shallowRenderer = createRenderer();
+ shallowRenderer.render();
+
+ // Calling cDU might lead to problems with host component references.
+ // Since our components aren't really mounted, refs won't be available.
+ expect(logs).toEqual(['getDerivedStateFromProps']);
+
+ logs.splice(0);
+
+ const instance = shallowRenderer.getMountedInstance();
+ instance.setState({});
+
+ expect(logs).toEqual(['shouldComponentUpdate']);
+
+ logs.splice(0);
+
+ shallowRenderer.render();
+
+ // The previous shallow renderer did not trigger cDU for props changes.
+ expect(logs).toEqual(['getDerivedStateFromProps', 'shouldComponentUpdate']);
+ });
+
+ it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
+ class Component extends React.Component {
+ state = {};
+ static getDerivedStateFromProps() {
+ return null;
+ }
+ componentWillMount() {
+ throw Error('unexpected');
+ }
+ componentWillReceiveProps() {
+ throw Error('unexpected');
+ }
+ componentWillUpdate() {
+ throw Error('unexpected');
+ }
+ render() {
+ return null;
+ }
+ }
+
+ const shallowRenderer = createRenderer();
+ expect(() => shallowRenderer.render()).toWarnDev(
+ 'Defines both componentWillReceiveProps() and static getDerivedStateFromProps()',
+ );
+ });
+
it('should only render 1 level deep', () => {
function Parent() {
return (
@@ -422,11 +478,10 @@ describe('ReactShallowRenderer', () => {
expect(result).toEqual();
});
- it('passes expected params to component lifecycle methods', () => {
+ it('passes expected params to legacy component lifecycle methods', () => {
const componentDidUpdateParams = [];
const componentWillReceivePropsParams = [];
const componentWillUpdateParams = [];
- const getDerivedStateFromPropsParams = [];
const setStateParams = [];
const shouldComponentUpdateParams = [];
@@ -448,10 +503,6 @@ describe('ReactShallowRenderer', () => {
componentDidUpdate(...args) {
componentDidUpdateParams.push(...args);
}
- static getDerivedStateFromProps(...args) {
- getDerivedStateFromPropsParams.push(args);
- return null;
- }
UNSAFE_componentWillReceiveProps(...args) {
componentWillReceivePropsParams.push(...args);
this.setState((...innerArgs) => {
@@ -472,22 +523,10 @@ describe('ReactShallowRenderer', () => {
}
const shallowRenderer = createRenderer();
-
- // The only lifecycle hook that should be invoked on initial render
- // Is the static getDerivedStateFromProps() methods
- expect(() =>
- shallowRenderer.render(
- React.createElement(SimpleComponent, initialProp),
- initialContext,
- ),
- ).toWarnDev(
- 'SimpleComponent: Defines both componentWillReceiveProps() and static ' +
- 'getDerivedStateFromProps() methods. We recommend using ' +
- 'only getDerivedStateFromProps().',
+ shallowRenderer.render(
+ React.createElement(SimpleComponent, initialProp),
+ initialContext,
);
- expect(getDerivedStateFromPropsParams).toEqual([
- [initialProp, initialState],
- ]);
expect(componentDidUpdateParams).toEqual([]);
expect(componentWillReceivePropsParams).toEqual([]);
expect(componentWillUpdateParams).toEqual([]);
@@ -504,10 +543,6 @@ describe('ReactShallowRenderer', () => {
updatedContext,
]);
expect(setStateParams).toEqual([initialState, initialProp]);
- expect(getDerivedStateFromPropsParams).toEqual([
- [initialProp, initialState],
- [updatedProp, initialState],
- ]);
expect(shouldComponentUpdateParams).toEqual([
updatedProp,
updatedState,
@@ -521,6 +556,72 @@ describe('ReactShallowRenderer', () => {
expect(componentDidUpdateParams).toEqual([]);
});
+ it('passes expected params to new component lifecycle methods', () => {
+ const componentDidUpdateParams = [];
+ const getDerivedStateFromPropsParams = [];
+ const shouldComponentUpdateParams = [];
+
+ const initialProp = {prop: 'init prop'};
+ const initialState = {state: 'init state'};
+ const initialContext = {context: 'init context'};
+ const updatedProp = {prop: 'updated prop'};
+ const updatedContext = {context: 'updated context'};
+
+ class SimpleComponent extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.state = initialState;
+ }
+ static contextTypes = {
+ context: PropTypes.string,
+ };
+ componentDidUpdate(...args) {
+ componentDidUpdateParams.push(...args);
+ }
+ static getDerivedStateFromProps(...args) {
+ getDerivedStateFromPropsParams.push(args);
+ return null;
+ }
+ shouldComponentUpdate(...args) {
+ shouldComponentUpdateParams.push(...args);
+ return true;
+ }
+ render() {
+ return null;
+ }
+ }
+
+ const shallowRenderer = createRenderer();
+
+ // The only lifecycle hook that should be invoked on initial render
+ // Is the static getDerivedStateFromProps() methods
+ shallowRenderer.render(
+ React.createElement(SimpleComponent, initialProp),
+ initialContext,
+ );
+ expect(getDerivedStateFromPropsParams).toEqual([
+ [initialProp, initialState],
+ ]);
+ expect(componentDidUpdateParams).toEqual([]);
+ expect(shouldComponentUpdateParams).toEqual([]);
+
+ // Lifecycle hooks should be invoked with the correct prev/next params on update.
+ shallowRenderer.render(
+ React.createElement(SimpleComponent, updatedProp),
+ updatedContext,
+ );
+ expect(getDerivedStateFromPropsParams).toEqual([
+ [initialProp, initialState],
+ [updatedProp, initialState],
+ ]);
+ expect(shouldComponentUpdateParams).toEqual([
+ updatedProp,
+ initialState,
+ updatedContext,
+ ]);
+ expect(componentDidUpdateParams).toEqual([]);
+ });
+
it('can shallowly render components with ref as function', () => {
class SimpleComponent extends React.Component {
state = {clicked: false};
diff --git a/packages/react/src/__tests__/ReactStrictMode-test.internal.js b/packages/react/src/__tests__/ReactStrictMode-test.internal.js
index 5bab8df2dd092..edbc5175a0b35 100644
--- a/packages/react/src/__tests__/ReactStrictMode-test.internal.js
+++ b/packages/react/src/__tests__/ReactStrictMode-test.internal.js
@@ -42,18 +42,9 @@ describe('ReactStrictMode', () => {
componentDidUpdate() {
log.push('componentDidUpdate');
}
- UNSAFE_componentWillMount() {
- log.push('componentWillMount');
- }
- UNSAFE_componentWillReceiveProps() {
- log.push('componentWillReceiveProps');
- }
componentWillUnmount() {
log.push('componentWillUnmount');
}
- UNSAFE_componentWillUpdate() {
- log.push('componentWillUpdate');
- }
shouldComponentUpdate() {
log.push('shouldComponentUpdate');
return shouldComponentUpdate;
@@ -64,22 +55,13 @@ describe('ReactStrictMode', () => {
}
}
- let component;
-
- expect(() => {
- component = ReactTestRenderer.create();
- }).toWarnDev(
- 'ClassComponent: Defines both componentWillReceiveProps() ' +
- 'and static getDerivedStateFromProps() methods. ' +
- 'We recommend using only getDerivedStateFromProps().',
- );
+ const component = ReactTestRenderer.create();
expect(log).toEqual([
'constructor',
'constructor',
'getDerivedStateFromProps',
'getDerivedStateFromProps',
- 'componentWillMount',
'render',
'render',
'componentDidMount',
@@ -90,11 +72,9 @@ describe('ReactStrictMode', () => {
component.update();
expect(log).toEqual([
- 'componentWillReceiveProps',
'getDerivedStateFromProps',
'getDerivedStateFromProps',
'shouldComponentUpdate',
- 'componentWillUpdate',
'render',
'render',
'componentDidUpdate',
@@ -105,7 +85,6 @@ describe('ReactStrictMode', () => {
component.update();
expect(log).toEqual([
- 'componentWillReceiveProps',
'getDerivedStateFromProps',
'getDerivedStateFromProps',
'shouldComponentUpdate',
diff --git a/packages/react/src/__tests__/createReactClassIntegration-test.js b/packages/react/src/__tests__/createReactClassIntegration-test.js
index aa46acf4fc476..c6156e7ea80e1 100644
--- a/packages/react/src/__tests__/createReactClassIntegration-test.js
+++ b/packages/react/src/__tests__/createReactClassIntegration-test.js
@@ -449,4 +449,34 @@ describe('create-react-class-integration', () => {
ReactDOM.render(, document.createElement('div')),
).toWarnDev('Did not properly initialize state during construction.');
});
+
+ it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
+ const Component = createReactClass({
+ statics: {
+ getDerivedStateFromProps: function() {
+ return null;
+ },
+ },
+ componentWillMount: function() {
+ throw Error('unexpected');
+ },
+ componentWillReceiveProps: function() {
+ throw Error('unexpected');
+ },
+ componentWillUpdate: function() {
+ throw Error('unexpected');
+ },
+ getInitialState: function() {
+ return {};
+ },
+ render: function() {
+ return null;
+ },
+ });
+
+ expect(() => {
+ ReactDOM.render(, document.createElement('div'));
+ }).toWarnDev('Defines both componentWillReceiveProps');
+ ReactDOM.render(, document.createElement('div'));
+ });
});