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