@samitouri / QOS-React-2 / commits / a7b9f98e7a

React lifecycles compat (#12105)

* Suppress unsafe/deprecation warnings for polyfilled components. * Don't invoke deprecated lifecycles if static gDSFP exists. * Applied recent changes to server rendering also

Brian Vaughn committed Jan 29, 2018 at 08:06 UTC a7b9f98e7abab7305978b4b36881c8267a450097
12 files changed +578 -105
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js
+55
@@ -24,6 +24,10 @@ describe('ReactComponentLifeCycle', () => {
24 ReactDOM = require('react-dom');
25 });
26
27 + afterEach(() => {
28 + jest.resetModules();
29 + });
30 +
31 // TODO (RFC #6) Merge this back into ReactComponentLifeCycles-test once
32 // the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
33 it('warns about deprecated unsafe lifecycles', function() {
@@ -55,4 +59,55 @@ describe('ReactComponentLifeCycle', () => {
59 ReactDOM.render(<MyComponent x={2} />, container);
60 ReactDOM.render(<MyComponent key="new" x={1} />, container);
61 });
62 +
63 + describe('react-lifecycles-compat', () => {
64 + // TODO Replace this with react-lifecycles-compat once it's been published
65 + function polyfill(Component) {
66 + Component.prototype.componentWillMount = function() {};
67 + Component.prototype.componentWillMount.__suppressDeprecationWarning = true;
68 + Component.prototype.componentWillReceiveProps = function() {};
69 + Component.prototype.componentWillReceiveProps.__suppressDeprecationWarning = true;
70 + }
71 +
72 + it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
73 + class PolyfilledComponent extends React.Component {
74 + state = {};
75 + static getDerivedStateFromProps() {
76 + return null;
77 + }
78 + render() {
79 + return null;
80 + }
81 + }
82 +
83 + polyfill(PolyfilledComponent);
84 +
85 + const container = document.createElement('div');
86 + ReactDOM.render(<PolyfilledComponent />, container);
87 + });
88 +
89 + it('should not warn about unsafe lifecycles within "strict" tree for polyfilled components', () => {
90 + const {StrictMode} = React;
91 +
92 + class PolyfilledComponent extends React.Component {
93 + state = {};
94 + static getDerivedStateFromProps() {
95 + return null;
96 + }
97 + render() {
98 + return null;
99 + }
100 + }
101 +
102 + polyfill(PolyfilledComponent);
103 +
104 + const container = document.createElement('div');
105 + ReactDOM.render(
106 + <StrictMode>
107 + <PolyfilledComponent />
108 + </StrictMode>,
109 + container,
110 + );
111 + });
112 + });
113 });
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+127 -23
@@ -499,7 +499,7 @@ describe('ReactComponentLifeCycle', () => {
499 expect(instance.state.stateField).toBe('goodbye');
500 });
501
502 - it('should call nested lifecycle methods in the right order', () => {
502 + it('should call nested legacy lifecycle methods in the right order', () => {
503 let log;
504 const logger = function(msg) {
505 return function() {
@@ -509,11 +509,6 @@ describe('ReactComponentLifeCycle', () => {
509 };
510 };
511 class Outer extends React.Component {
512 - state = {};
513 - static getDerivedStateFromProps(props, prevState) {
514 - log.push('outer getDerivedStateFromProps');
515 - return null;
516 - }
512 UNSAFE_componentWillMount = logger('outer componentWillMount');
513 componentDidMount = logger('outer componentDidMount');
514 UNSAFE_componentWillReceiveProps = logger(
@@ -533,11 +528,6 @@ describe('ReactComponentLifeCycle', () => {
528 }
529
530 class Inner extends React.Component {
536 - state = {};
537 - static getDerivedStateFromProps(props, prevState) {
538 - log.push('inner getDerivedStateFromProps');
539 - return null;
540 - }
531 UNSAFE_componentWillMount = logger('inner componentWillMount');
532 componentDidMount = logger('inner componentDidMount');
533 UNSAFE_componentWillReceiveProps = logger(
@@ -554,18 +544,9 @@ describe('ReactComponentLifeCycle', () => {
544
545 const container = document.createElement('div');
546 log = [];
557 - expect(() => ReactDOM.render(<Outer x={1} />, container)).toWarnDev([
558 - 'Warning: Outer: Defines both componentWillReceiveProps() and static ' +
559 - 'getDerivedStateFromProps() methods. ' +
560 - 'We recommend using only getDerivedStateFromProps().',
561 - 'Warning: Inner: Defines both componentWillReceiveProps() and static ' +
562 - 'getDerivedStateFromProps() methods. ' +
563 - 'We recommend using only getDerivedStateFromProps().',
564 - ]);
547 + ReactDOM.render(<Outer x={1} />, container);
548 expect(log).toEqual([
566 - 'outer getDerivedStateFromProps',
549 'outer componentWillMount',
568 - 'inner getDerivedStateFromProps',
550 'inner componentWillMount',
551 'inner componentDidMount',
552 'outer componentDidMount',
@@ -576,11 +557,9 @@ describe('ReactComponentLifeCycle', () => {
557 ReactDOM.render(<Outer x={2} />, container);
558 expect(log).toEqual([
559 'outer componentWillReceiveProps',
579 - 'outer getDerivedStateFromProps',
560 'outer shouldComponentUpdate',
561 'outer componentWillUpdate',
562 'inner componentWillReceiveProps',
583 - 'inner getDerivedStateFromProps',
563 'inner shouldComponentUpdate',
564 'inner componentWillUpdate',
565 'inner componentDidUpdate',
@@ -595,6 +574,131 @@ describe('ReactComponentLifeCycle', () => {
574 ]);
575 });
576
577 + it('should call nested new lifecycle methods in the right order', () => {
578 + let log;
579 + const logger = function(msg) {
580 + return function() {
581 + // return true for shouldComponentUpdate
582 + log.push(msg);
583 + return true;
584 + };
585 + };
586 + class Outer extends React.Component {
587 + state = {};
588 + static getDerivedStateFromProps(props, prevState) {
589 + log.push('outer getDerivedStateFromProps');
590 + return null;
591 + }
592 + componentDidMount = logger('outer componentDidMount');
593 + shouldComponentUpdate = logger('outer shouldComponentUpdate');
594 + componentDidUpdate = logger('outer componentDidUpdate');
595 + componentWillUnmount = logger('outer componentWillUnmount');
596 + render() {
597 + return (
598 + <div>
599 + <Inner x={this.props.x} />
600 + </div>
601 + );
602 + }
603 + }
604 +
605 + class Inner extends React.Component {
606 + state = {};
607 + static getDerivedStateFromProps(props, prevState) {
608 + log.push('inner getDerivedStateFromProps');
609 + return null;
610 + }
611 + componentDidMount = logger('inner componentDidMount');
612 + shouldComponentUpdate = logger('inner shouldComponentUpdate');
613 + componentDidUpdate = logger('inner componentDidUpdate');
614 + componentWillUnmount = logger('inner componentWillUnmount');
615 + render() {
616 + return <span>{this.props.x}</span>;
617 + }
618 + }
619 +
620 + const container = document.createElement('div');
621 + log = [];
622 + ReactDOM.render(<Outer x={1} />, container);
623 + expect(log).toEqual([
624 + 'outer getDerivedStateFromProps',
625 + 'inner getDerivedStateFromProps',
626 + 'inner componentDidMount',
627 + 'outer componentDidMount',
628 + ]);
629 +
630 + // Dedup warnings
631 + log = [];
632 + ReactDOM.render(<Outer x={2} />, container);
633 + expect(log).toEqual([
634 + 'outer getDerivedStateFromProps',
635 + 'outer shouldComponentUpdate',
636 + 'inner getDerivedStateFromProps',
637 + 'inner shouldComponentUpdate',
638 + 'inner componentDidUpdate',
639 + 'outer componentDidUpdate',
640 + ]);
641 +
642 + log = [];
643 + ReactDOM.unmountComponentAtNode(container);
644 + expect(log).toEqual([
645 + 'outer componentWillUnmount',
646 + 'inner componentWillUnmount',
647 + ]);
648 + });
649 +
650 + it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
651 + class Component extends React.Component {
652 + state = {};
653 + static getDerivedStateFromProps() {
654 + return null;
655 + }
656 + componentWillMount() {
657 + throw Error('unexpected');
658 + }
659 + componentWillReceiveProps() {
660 + throw Error('unexpected');
661 + }
662 + componentWillUpdate() {
663 + throw Error('unexpected');
664 + }
665 + render() {
666 + return null;
667 + }
668 + }
669 +
670 + const container = document.createElement('div');
671 + expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
672 + 'Defines both componentWillReceiveProps',
673 + );
674 + });
675 +
676 + it('should not invoke new unsafe lifecycles (cWM/cWRP/cWU) if static gDSFP is present', () => {
677 + class Component extends React.Component {
678 + state = {};
679 + static getDerivedStateFromProps() {
680 + return null;
681 + }
682 + UNSAFE_componentWillMount() {
683 + throw Error('unexpected');
684 + }
685 + UNSAFE_componentWillReceiveProps() {
686 + throw Error('unexpected');
687 + }
688 + UNSAFE_componentWillUpdate() {
689 + throw Error('unexpected');
690 + }
691 + render() {
692 + return null;
693 + }
694 + }
695 +
696 + const container = document.createElement('div');
697 + expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
698 + 'Defines both componentWillReceiveProps',
699 + );
700 + });
701 +
702 it('calls effects on module-pattern component', function() {
703 const log = [];
704
packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.internal.js
+49
@@ -22,6 +22,29 @@ describe('ReactDOMServerLifecycles', () => {
22 ReactDOMServer = require('react-dom/server');
23 });
24
25 + afterEach(() => {
26 + jest.resetModules();
27 + });
28 +
29 + it('should not invoke cWM if static gDSFP is present', () => {
30 + class Component extends React.Component {
31 + state = {};
32 + static getDerivedStateFromProps() {
33 + return null;
34 + }
35 + componentWillMount() {
36 + throw Error('unexpected');
37 + }
38 + render() {
39 + return null;
40 + }
41 + }
42 +
43 + expect(() => ReactDOMServer.renderToString(<Component />)).toWarnDev(
44 + 'Component: componentWillMount() is deprecated and will be removed in the next major version.',
45 + );
46 + });
47 +
48 // TODO (RFC #6) Merge this back into ReactDOMServerLifecycles-test once
49 // the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
50 it('should warn about deprecated lifecycle hooks', () => {
@@ -40,4 +63,30 @@ describe('ReactDOMServerLifecycles', () => {
63 // De-duped
64 ReactDOMServer.renderToString(<Component />);
65 });
66 +
67 + describe('react-lifecycles-compat', () => {
68 + // TODO Replace this with react-lifecycles-compat once it's been published
69 + function polyfill(Component) {
70 + Component.prototype.componentWillMount = function() {};
71 + Component.prototype.componentWillMount.__suppressDeprecationWarning = true;
72 + Component.prototype.componentWillReceiveProps = function() {};
73 + Component.prototype.componentWillReceiveProps.__suppressDeprecationWarning = true;
74 + }
75 +
76 + it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
77 + class PolyfilledComponent extends React.Component {
78 + state = {};
79 + static getDerivedStateFromProps() {
80 + return null;
81 + }
82 + render() {
83 + return null;
84 + }
85 + }
86 +
87 + polyfill(PolyfilledComponent);
88 +
89 + ReactDOMServer.renderToString(<PolyfilledComponent />);
90 + });
91 + });
92 });
packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js
+54 -1
@@ -33,7 +33,7 @@ describe('ReactDOMServerLifecycles', () => {
33 resetModules();
34 });
35
36 - it('should invoke the correct lifecycle hooks', () => {
36 + it('should invoke the correct legacy lifecycle hooks', () => {
37 const log = [];
38
39 class Outer extends React.Component {
@@ -65,6 +65,59 @@ describe('ReactDOMServerLifecycles', () => {
65 ]);
66 });
67
68 + it('should invoke the correct new lifecycle hooks', () => {
69 + const log = [];
70 +
71 + class Outer extends React.Component {
72 + state = {};
73 + static getDerivedStateFromProps() {
74 + log.push('outer getDerivedStateFromProps');
75 + return null;
76 + }
77 + render() {
78 + log.push('outer render');
79 + return <Inner />;
80 + }
81 + }
82 +
83 + class Inner extends React.Component {
84 + state = {};
85 + static getDerivedStateFromProps() {
86 + log.push('inner getDerivedStateFromProps');
87 + return null;
88 + }
89 + render() {
90 + log.push('inner render');
91 + return null;
92 + }
93 + }
94 +
95 + ReactDOMServer.renderToString(<Outer />);
96 + expect(log).toEqual([
97 + 'outer getDerivedStateFromProps',
98 + 'outer render',
99 + 'inner getDerivedStateFromProps',
100 + 'inner render',
101 + ]);
102 + });
103 +
104 + it('should not invoke unsafe cWM if static gDSFP is present', () => {
105 + class Component extends React.Component {
106 + state = {};
107 + static getDerivedStateFromProps() {
108 + return null;
109 + }
110 + UNSAFE_componentWillMount() {
111 + throw Error('unexpected');
112 + }
113 + render() {
114 + return null;
115 + }
116 + }
117 +
118 + ReactDOMServer.renderToString(<Component />);
119 + });
120 +
121 it('should update instance.state with value returned from getDerivedStateFromProps', () => {
122 class Grandparent extends React.Component {
123 state = {
packages/react-dom/src/server/ReactPartialRenderer.js
+12 -3
@@ -514,7 +514,10 @@ function resolve(
514 if (inst.UNSAFE_componentWillMount || inst.componentWillMount) {
515 if (inst.componentWillMount) {
516 if (__DEV__) {
517 - if (warnAboutDeprecatedLifecycles) {
517 + if (
518 + warnAboutDeprecatedLifecycles &&
519 + inst.componentWillMount.__suppressDeprecationWarning !== true
520 + ) {
521 const componentName = getComponentName(Component) || 'Unknown';
522
523 if (!didWarnAboutDeprecatedWillMount[componentName]) {
@@ -534,8 +537,14 @@ function resolve(
537 }
538 }
539
537 - inst.componentWillMount();
538 - } else {
540 + // In order to support react-lifecycles-compat polyfilled components,
541 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
542 + if (typeof Component.getDerivedStateFromProps !== 'function') {
543 + inst.componentWillMount();
544 + }
545 + } else if (typeof Component.getDerivedStateFromProps !== 'function') {
546 + // In order to support react-lifecycles-compat polyfilled components,
547 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
548 inst.UNSAFE_componentWillMount();
549 }
550 if (queue.length) {
packages/react-reconciler/src/ReactFiberClassComponent.js
+25 -12
@@ -524,8 +524,11 @@ export default function(
524
525 if (typeof type.getDerivedStateFromProps === 'function') {
526 if (__DEV__) {
527 + // Don't warn about react-lifecycles-compat polyfilled components
528 if (
528 - typeof instance.componentWillReceiveProps === 'function' ||
529 + (typeof instance.componentWillReceiveProps === 'function' &&
530 + instance.componentWillReceiveProps.__suppressDeprecationWarning !==
531 + true) ||
532 typeof instance.UNSAFE_componentWillReceiveProps === 'function'
533 ) {
534 const componentName = getComponentName(workInProgress) || 'Unknown';
@@ -627,9 +630,12 @@ export default function(
630 }
631 }
632
633 + // In order to support react-lifecycles-compat polyfilled components,
634 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
635 if (
631 - typeof instance.UNSAFE_componentWillMount === 'function' ||
632 - typeof instance.componentWillMount === 'function'
636 + (typeof instance.UNSAFE_componentWillMount === 'function' ||
637 + typeof instance.componentWillMount === 'function') &&
638 + typeof workInProgress.type.getDerivedStateFromProps !== 'function'
639 ) {
640 callComponentWillMount(workInProgress, instance);
641 // If we had additional state updates during this life-cycle, let's
@@ -775,17 +781,21 @@ export default function(
781 // ever the previously attempted to render - not the "current". However,
782 // during componentDidUpdate we pass the "current" props.
783
784 + // In order to support react-lifecycles-compat polyfilled components,
785 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
786 if (
787 (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
788 typeof instance.componentWillReceiveProps === 'function') &&
781 - (oldProps !== newProps || oldContext !== newContext)
789 + typeof workInProgress.type.getDerivedStateFromProps !== 'function'
790 ) {
783 - callComponentWillReceiveProps(
784 - workInProgress,
785 - instance,
786 - newProps,
787 - newContext,
788 - );
791 + if (oldProps !== newProps || oldContext !== newContext) {
792 + callComponentWillReceiveProps(
793 + workInProgress,
794 + instance,
795 + newProps,
796 + newContext,
797 + );
798 + }
799 }
800
801 let partialState;
@@ -856,9 +866,12 @@ export default function(
866 );
867
868 if (shouldUpdate) {
869 + // In order to support react-lifecycles-compat polyfilled components,
870 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
871 if (
860 - typeof instance.UNSAFE_componentWillUpdate === 'function' ||
861 - typeof instance.componentWillUpdate === 'function'
872 + (typeof instance.UNSAFE_componentWillUpdate === 'function' ||
873 + typeof instance.componentWillUpdate === 'function') &&
874 + typeof workInProgress.type.getDerivedStateFromProps !== 'function'
875 ) {
876 if (typeof instance.componentWillUpdate === 'function') {
877 startPhaseTimer(workInProgress, 'componentWillUpdate');
packages/react-reconciler/src/ReactStrictModeWarnings.js
+20
@@ -199,6 +199,16 @@ if (__DEV__) {
199 return;
200 }
201
202 + // Don't warn about react-lifecycles-compat polyfilled components.
203 + // Note that it is sufficient to check for the presence of a
204 + // single lifecycle, componentWillMount, with the polyfill flag.
205 + if (
206 + typeof instance.componentWillMount === 'function' &&
207 + instance.componentWillMount.__suppressDeprecationWarning === true
208 + ) {
209 + return;
210 + }
211 +
212 if (typeof instance.componentWillMount === 'function') {
213 pendingComponentWillMountWarnings.push(fiber);
214 }
@@ -225,6 +235,16 @@ if (__DEV__) {
235 return;
236 }
237
238 + // Don't warn about react-lifecycles-compat polyfilled components.
239 + // Note that it is sufficient to check for the presence of a
240 + // single lifecycle, componentWillMount, with the polyfill flag.
241 + if (
242 + typeof instance.componentWillMount === 'function' &&
243 + instance.componentWillMount.__suppressDeprecationWarning === true
244 + ) {
245 + return;
246 + }
247 +
248 let warningsForRoot;
249 if (!pendingUnsafeLifecycleWarnings.has(strictRoot)) {
250 warningsForRoot = {
packages/react-test-renderer/src/ReactShallowRenderer.js
+37 -8
@@ -180,7 +180,12 @@ class ReactShallowRenderer {
180
181 if (typeof this._instance.componentWillMount === 'function') {
182 if (__DEV__) {
183 - if (warnAboutDeprecatedLifecycles) {
183 + // Don't warn about react-lifecycles-compat polyfilled components
184 + if (
185 + warnAboutDeprecatedLifecycles &&
186 + this._instance.componentWillMount.__suppressDeprecationWarning !==
187 + true
188 + ) {
189 const componentName = getName(element.type, this._instance);
190 if (!didWarnAboutLegacyWillMount[componentName]) {
191 warning(
@@ -198,8 +203,15 @@ class ReactShallowRenderer {
203 }
204 }
205 }
201 - this._instance.componentWillMount();
202 - } else {
206 +
207 + // In order to support react-lifecycles-compat polyfilled components,
208 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
209 + if (typeof element.type.getDerivedStateFromProps !== 'function') {
210 + this._instance.componentWillMount();
211 + }
212 + } else if (typeof element.type.getDerivedStateFromProps !== 'function') {
213 + // In order to support react-lifecycles-compat polyfilled components,
214 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
215 this._instance.UNSAFE_componentWillMount();
216 }
217
@@ -242,10 +254,17 @@ class ReactShallowRenderer {
254 }
255 }
256 }
245 - this._instance.componentWillReceiveProps(props, context);
257 + // In order to support react-lifecycles-compat polyfilled components,
258 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
259 + if (typeof element.type.getDerivedStateFromProps !== 'function') {
260 + this._instance.componentWillReceiveProps(props, context);
261 + }
262 } else if (
247 - typeof this._instance.UNSAFE_componentWillReceiveProps === 'function'
263 + typeof this._instance.UNSAFE_componentWillReceiveProps === 'function' &&
264 + typeof element.type.getDerivedStateFromProps !== 'function'
265 ) {
266 + // In order to support react-lifecycles-compat polyfilled components,
267 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
268 this._instance.UNSAFE_componentWillReceiveProps(props, context);
269 }
270
@@ -292,10 +311,17 @@ class ReactShallowRenderer {
311 }
312 }
313
295 - this._instance.componentWillUpdate(props, state, context);
314 + // In order to support react-lifecycles-compat polyfilled components,
315 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
316 + if (typeof type.getDerivedStateFromProps !== 'function') {
317 + this._instance.componentWillUpdate(props, state, context);
318 + }
319 } else if (
297 - typeof this._instance.UNSAFE_componentWillUpdate === 'function'
320 + typeof this._instance.UNSAFE_componentWillUpdate === 'function' &&
321 + typeof type.getDerivedStateFromProps !== 'function'
322 ) {
323 + // In order to support react-lifecycles-compat polyfilled components,
324 + // Unsafe lifecycles should not be invoked for any component with the new gDSFP.
325 this._instance.UNSAFE_componentWillUpdate(props, state, context);
326 }
327 }
@@ -316,8 +342,11 @@ class ReactShallowRenderer {
342
343 if (typeof type.getDerivedStateFromProps === 'function') {
344 if (__DEV__) {
345 + // Don't warn about react-lifecycles-compat polyfilled components
346 if (
320 - typeof this._instance.componentWillReceiveProps === 'function' ||
347 + (typeof this._instance.componentWillReceiveProps === 'function' &&
348 + this._instance.componentWillReceiveProps
349 + .__suppressDeprecationWarning !== true) ||
350 typeof this._instance.UNSAFE_componentWillReceiveProps === 'function'
351 ) {
352 const componentName = getName(type, this._instance);
packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.internal.js
+31
@@ -23,6 +23,10 @@ describe('ReactShallowRenderer', () => {
23 React = require('react');
24 });
25
26 + afterEach(() => {
27 + jest.resetModules();
28 + });
29 +
30 // TODO (RFC #6) Merge this back into ReactShallowRenderer-test once
31 // the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
32 it('should warn if deprecated lifecycles exist', () => {
@@ -50,4 +54,31 @@ describe('ReactShallowRenderer', () => {
54 // Verify no duplicate warnings
55 shallowRenderer.render(<ComponentWithWarnings />);
56 });
57 +
58 + describe('react-lifecycles-compat', () => {
59 + // TODO Replace this with react-lifecycles-compat once it's been published
60 + function polyfill(Component) {
61 + Component.prototype.componentWillMount = function() {};
62 + Component.prototype.componentWillMount.__suppressDeprecationWarning = true;
63 + Component.prototype.componentWillReceiveProps = function() {};
64 + Component.prototype.componentWillReceiveProps.__suppressDeprecationWarning = true;
65 + }
66 +
67 + it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
68 + class PolyfilledComponent extends React.Component {
69 + state = {};
70 + static getDerivedStateFromProps() {
71 + return null;
72 + }
73 + render() {
74 + return null;
75 + }
76 + }
77 +
78 + polyfill(PolyfilledComponent);
79 +
80 + const shallowRenderer = createRenderer();
81 + shallowRenderer.render(<PolyfilledComponent />);
82 + });
83 + });
84 });
packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
+137 -36
@@ -23,13 +23,11 @@ describe('ReactShallowRenderer', () => {
23 React = require('react');
24 });
25
26 - it('should call all of the lifecycle hooks', () => {
26 + it('should call all of the legacy lifecycle hooks', () => {
27 const logs = [];
28 const logger = message => () => logs.push(message) || true;
29
30 class SomeComponent extends React.Component {
31 - state = {};
32 - static getDerivedStateFromProps = logger('getDerivedStateFromProps');
31 UNSAFE_componentWillMount = logger('componentWillMount');
32 componentDidMount = logger('componentDidMount');
33 UNSAFE_componentWillReceiveProps = logger('componentWillReceiveProps');
@@ -43,16 +41,11 @@ describe('ReactShallowRenderer', () => {
41 }
42
43 const shallowRenderer = createRenderer();
46 -
47 - expect(() => shallowRenderer.render(<SomeComponent foo={1} />)).toWarnDev(
48 - 'Warning: SomeComponent: Defines both componentWillReceiveProps() and static ' +
49 - 'getDerivedStateFromProps() methods. ' +
50 - 'We recommend using only getDerivedStateFromProps().',
51 - );
44 + shallowRenderer.render(<SomeComponent foo={1} />);
45
46 // Calling cDU might lead to problems with host component references.
47 // Since our components aren't really mounted, refs won't be available.
55 - expect(logs).toEqual(['getDerivedStateFromProps', 'componentWillMount']);
48 + expect(logs).toEqual(['componentWillMount']);
49
50 logs.splice(0);
51
@@ -68,12 +61,75 @@ describe('ReactShallowRenderer', () => {
61 // The previous shallow renderer did not trigger cDU for props changes.
62 expect(logs).toEqual([
63 'componentWillReceiveProps',
71 - 'getDerivedStateFromProps',
64 'shouldComponentUpdate',
65 'componentWillUpdate',
66 ]);
67 });
68
69 + it('should call all of the new lifecycle hooks', () => {
70 + const logs = [];
71 + const logger = message => () => logs.push(message) || true;
72 +
73 + class SomeComponent extends React.Component {
74 + state = {};
75 + static getDerivedStateFromProps = logger('getDerivedStateFromProps');
76 + componentDidMount = logger('componentDidMount');
77 + shouldComponentUpdate = logger('shouldComponentUpdate');
78 + componentDidUpdate = logger('componentDidUpdate');
79 + componentWillUnmount = logger('componentWillUnmount');
80 + render() {
81 + return <div />;
82 + }
83 + }
84 +
85 + const shallowRenderer = createRenderer();
86 + shallowRenderer.render(<SomeComponent foo={1} />);
87 +
88 + // Calling cDU might lead to problems with host component references.
89 + // Since our components aren't really mounted, refs won't be available.
90 + expect(logs).toEqual(['getDerivedStateFromProps']);
91 +
92 + logs.splice(0);
93 +
94 + const instance = shallowRenderer.getMountedInstance();
95 + instance.setState({});
96 +
97 + expect(logs).toEqual(['shouldComponentUpdate']);
98 +
99 + logs.splice(0);
100 +
101 + shallowRenderer.render(<SomeComponent foo={2} />);
102 +
103 + // The previous shallow renderer did not trigger cDU for props changes.
104 + expect(logs).toEqual(['getDerivedStateFromProps', 'shouldComponentUpdate']);
105 + });
106 +
107 + it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
108 + class Component extends React.Component {
109 + state = {};
110 + static getDerivedStateFromProps() {
111 + return null;
112 + }
113 + componentWillMount() {
114 + throw Error('unexpected');
115 + }
116 + componentWillReceiveProps() {
117 + throw Error('unexpected');
118 + }
119 + componentWillUpdate() {
120 + throw Error('unexpected');
121 + }
122 + render() {
123 + return null;
124 + }
125 + }
126 +
127 + const shallowRenderer = createRenderer();
128 + expect(() => shallowRenderer.render(<Component foo={2} />)).toWarnDev(
129 + 'Defines both componentWillReceiveProps() and static getDerivedStateFromProps()',
130 + );
131 + });
132 +
133 it('should only render 1 level deep', () => {
134 function Parent() {
135 return (
@@ -422,11 +478,10 @@ describe('ReactShallowRenderer', () => {
478 expect(result).toEqual(<div />);
479 });
480
425 - it('passes expected params to component lifecycle methods', () => {
481 + it('passes expected params to legacy component lifecycle methods', () => {
482 const componentDidUpdateParams = [];
483 const componentWillReceivePropsParams = [];
484 const componentWillUpdateParams = [];
429 - const getDerivedStateFromPropsParams = [];
485 const setStateParams = [];
486 const shouldComponentUpdateParams = [];
487
@@ -448,10 +503,6 @@ describe('ReactShallowRenderer', () => {
503 componentDidUpdate(...args) {
504 componentDidUpdateParams.push(...args);
505 }
451 - static getDerivedStateFromProps(...args) {
452 - getDerivedStateFromPropsParams.push(args);
453 - return null;
454 - }
506 UNSAFE_componentWillReceiveProps(...args) {
507 componentWillReceivePropsParams.push(...args);
508 this.setState((...innerArgs) => {
@@ -472,22 +523,10 @@ describe('ReactShallowRenderer', () => {
523 }
524
525 const shallowRenderer = createRenderer();
475 -
476 - // The only lifecycle hook that should be invoked on initial render
477 - // Is the static getDerivedStateFromProps() methods
478 - expect(() =>
479 - shallowRenderer.render(
480 - React.createElement(SimpleComponent, initialProp),
481 - initialContext,
482 - ),
483 - ).toWarnDev(
484 - 'SimpleComponent: Defines both componentWillReceiveProps() and static ' +
485 - 'getDerivedStateFromProps() methods. We recommend using ' +
486 - 'only getDerivedStateFromProps().',
526 + shallowRenderer.render(
527 + React.createElement(SimpleComponent, initialProp),
528 + initialContext,
529 );
488 - expect(getDerivedStateFromPropsParams).toEqual([
489 - [initialProp, initialState],
490 - ]);
530 expect(componentDidUpdateParams).toEqual([]);
531 expect(componentWillReceivePropsParams).toEqual([]);
532 expect(componentWillUpdateParams).toEqual([]);
@@ -504,10 +543,6 @@ describe('ReactShallowRenderer', () => {
543 updatedContext,
544 ]);
545 expect(setStateParams).toEqual([initialState, initialProp]);
507 - expect(getDerivedStateFromPropsParams).toEqual([
508 - [initialProp, initialState],
509 - [updatedProp, initialState],
510 - ]);
546 expect(shouldComponentUpdateParams).toEqual([
547 updatedProp,
548 updatedState,
@@ -521,6 +556,72 @@ describe('ReactShallowRenderer', () => {
556 expect(componentDidUpdateParams).toEqual([]);
557 });
558
559 + it('passes expected params to new component lifecycle methods', () => {
560 + const componentDidUpdateParams = [];
561 + const getDerivedStateFromPropsParams = [];
562 + const shouldComponentUpdateParams = [];
563 +
564 + const initialProp = {prop: 'init prop'};
565 + const initialState = {state: 'init state'};
566 + const initialContext = {context: 'init context'};
567 + const updatedProp = {prop: 'updated prop'};
568 + const updatedContext = {context: 'updated context'};
569 +
570 + class SimpleComponent extends React.Component {
571 + constructor(props, context) {
572 + super(props, context);
573 + this.state = initialState;
574 + }
575 + static contextTypes = {
576 + context: PropTypes.string,
577 + };
578 + componentDidUpdate(...args) {
579 + componentDidUpdateParams.push(...args);
580 + }
581 + static getDerivedStateFromProps(...args) {
582 + getDerivedStateFromPropsParams.push(args);
583 + return null;
584 + }
585 + shouldComponentUpdate(...args) {
586 + shouldComponentUpdateParams.push(...args);
587 + return true;
588 + }
589 + render() {
590 + return null;
591 + }
592 + }
593 +
594 + const shallowRenderer = createRenderer();
595 +
596 + // The only lifecycle hook that should be invoked on initial render
597 + // Is the static getDerivedStateFromProps() methods
598 + shallowRenderer.render(
599 + React.createElement(SimpleComponent, initialProp),
600 + initialContext,
601 + );
602 + expect(getDerivedStateFromPropsParams).toEqual([
603 + [initialProp, initialState],
604 + ]);
605 + expect(componentDidUpdateParams).toEqual([]);
606 + expect(shouldComponentUpdateParams).toEqual([]);
607 +
608 + // Lifecycle hooks should be invoked with the correct prev/next params on update.
609 + shallowRenderer.render(
610 + React.createElement(SimpleComponent, updatedProp),
611 + updatedContext,
612 + );
613 + expect(getDerivedStateFromPropsParams).toEqual([
614 + [initialProp, initialState],
615 + [updatedProp, initialState],
616 + ]);
617 + expect(shouldComponentUpdateParams).toEqual([
618 + updatedProp,
619 + initialState,
620 + updatedContext,
621 + ]);
622 + expect(componentDidUpdateParams).toEqual([]);
623 + });
624 +
625 it('can shallowly render components with ref as function', () => {
626 class SimpleComponent extends React.Component {
627 state = {clicked: false};
packages/react/src/__tests__/ReactStrictMode-test.internal.js
+1 -22
@@ -42,18 +42,9 @@ describe('ReactStrictMode', () => {
42 componentDidUpdate() {
43 log.push('componentDidUpdate');
44 }
45 - UNSAFE_componentWillMount() {
46 - log.push('componentWillMount');
47 - }
48 - UNSAFE_componentWillReceiveProps() {
49 - log.push('componentWillReceiveProps');
50 - }
45 componentWillUnmount() {
46 log.push('componentWillUnmount');
47 }
54 - UNSAFE_componentWillUpdate() {
55 - log.push('componentWillUpdate');
56 - }
48 shouldComponentUpdate() {
49 log.push('shouldComponentUpdate');
50 return shouldComponentUpdate;
@@ -64,22 +55,13 @@ describe('ReactStrictMode', () => {
55 }
56 }
57
67 - let component;
68 -
69 - expect(() => {
70 - component = ReactTestRenderer.create(<ClassComponent />);
71 - }).toWarnDev(
72 - 'ClassComponent: Defines both componentWillReceiveProps() ' +
73 - 'and static getDerivedStateFromProps() methods. ' +
74 - 'We recommend using only getDerivedStateFromProps().',
75 - );
58 + const component = ReactTestRenderer.create(<ClassComponent />);
59
60 expect(log).toEqual([
61 'constructor',
62 'constructor',
63 'getDerivedStateFromProps',
64 'getDerivedStateFromProps',
82 - 'componentWillMount',
65 'render',
66 'render',
67 'componentDidMount',
@@ -90,11 +72,9 @@ describe('ReactStrictMode', () => {
72
73 component.update(<ClassComponent />);
74 expect(log).toEqual([
93 - 'componentWillReceiveProps',
75 'getDerivedStateFromProps',
76 'getDerivedStateFromProps',
77 'shouldComponentUpdate',
97 - 'componentWillUpdate',
78 'render',
79 'render',
80 'componentDidUpdate',
@@ -105,7 +85,6 @@ describe('ReactStrictMode', () => {
85
86 component.update(<ClassComponent />);
87 expect(log).toEqual([
108 - 'componentWillReceiveProps',
88 'getDerivedStateFromProps',
89 'getDerivedStateFromProps',
90 'shouldComponentUpdate',
packages/react/src/__tests__/createReactClassIntegration-test.js
+30
@@ -449,4 +449,34 @@ describe('create-react-class-integration', () => {
449 ReactDOM.render(<Component />, document.createElement('div')),
450 ).toWarnDev('Did not properly initialize state during construction.');
451 });
452 +
453 + it('should not invoke deprecated lifecycles (cWM/cWRP/cWU) if new static gDSFP is present', () => {
454 + const Component = createReactClass({
455 + statics: {
456 + getDerivedStateFromProps: function() {
457 + return null;
458 + },
459 + },
460 + componentWillMount: function() {
461 + throw Error('unexpected');
462 + },
463 + componentWillReceiveProps: function() {
464 + throw Error('unexpected');
465 + },
466 + componentWillUpdate: function() {
467 + throw Error('unexpected');
468 + },
469 + getInitialState: function() {
470 + return {};
471 + },
472 + render: function() {
473 + return null;
474 + },
475 + });
476 +
477 + expect(() => {
478 + ReactDOM.render(<Component />, document.createElement('div'));
479 + }).toWarnDev('Defines both componentWillReceiveProps');
480 + ReactDOM.render(<Component foo={1} />, document.createElement('div'));
481 + });
482 });