@samitouri / QOS-React / commits / 97e2911508

RFC 6: Deprecate unsafe lifecycles (#12028)

* Added unsafe_* lifecycles and deprecation warnings If the old lifecycle hooks (componentWillMount, componentWillUpdate, componentWillReceiveProps) are detected, these methods will be called and a deprecation warning will be logged. (In other words, we do not check for both the presence of the old and new lifecycles.) This commit is expected to fail tests. * Ran lifecycle hook codemod over project This should handle the bulk of the updates. I will manually update TypeScript and CoffeeScript tests with another commit. The actual command run with this commit was: jscodeshift --parser=flow -t ../react-codemod/transforms/rename-unsafe-lifecycles.js ./packages/**/src/**/*.js * Manually migrated CoffeeScript and TypeScript tests * Added inline note to createReactClassIntegration-test Explaining why lifecycles hooks have not been renamed in this test. * Udated NativeMethodsMixin with new lifecycle hooks * Added static getDerivedStateFromProps to ReactPartialRenderer Also added a new set of tests focused on server side lifecycle hooks. * Added getDerivedStateFromProps to shallow renderer Also added warnings for several cases involving getDerivedStateFromProps() as well as the deprecated lifecycles. Also added tests for the above. * Dedupe and DEV-only deprecation warning in server renderer * Renamed unsafe_* prefix to UNSAFE_* to be more noticeable * Added getDerivedStateFromProps to ReactFiberClassComponent Also updated class component and lifecyle tests to cover the added functionality. * Warn about UNSAFE_componentWillRecieveProps misspelling * Added tests to createReactClassIntegration for new lifecycles * Added warning for stateless functional components with gDSFP * Added createReactClass test for static gDSFP * Moved lifecycle deprecation warnings behind (disabled) feature flag Updated tests accordingly, by temporarily splitting tests that were specific to this feature-flag into their own, internal tests. This was the only way I knew of to interact with the feature flag without breaking our build/dist tests. * Tidying up * Tweaked warning message wording slightly Replaced 'You may may have returned undefined.' with 'You may have returned undefined.' * Replaced truthy partialState checks with != null * Call getDerivedStateFromProps via .call(null) to prevent type access * Move shallow-renderer didWarn* maps off the instance * Only call getDerivedStateFromProps if props instance has changed * Avoid creating new state object if not necessary * Inject state as a param to callGetDerivedStateFromProps This value will be either workInProgress.memoizedState (for updates) or instance.state (for initialization). * Explicitly warn about uninitialized state before calling getDerivedStateFromProps. And added some new tests for this change. Also: * Improved a couple of falsy null/undefined checks to more explicitly check for null or undefined. * Made some small tweaks to ReactFiberClassComponent WRT when and how it reads instance.state and sets to null. * Improved wording for deprecation lifecycle warnings * Fix state-regression for module-pattern components Also add support for new static getDerivedStateFromProps method

Brian Vaughn committed Jan 19, 2018 at 09:36 UTC 97e2911508a2a7af6f50cf87ae503abe39842bef
38 files changed +1837 -265
packages/react-call-return/src/__tests__/ReactCallReturn-test.js
+1 -1
@@ -275,7 +275,7 @@ describe('ReactCallReturn', () => {
275 return ReactCallReturn.unstable_createReturn(this.props.children);
276 }
277
278 - componentWillMount() {
278 + UNSAFE_componentWillMount() {
279 ops.push(`Mount Return ${this.props.value}`);
280 }
281
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.internal.js new
+56
@@ -0,0 +1,56 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +let React;
13 +let ReactDOM;
14 +let ReactFeatureFlags;
15 +
16 +describe('ReactComponentLifeCycle', () => {
17 + beforeEach(() => {
18 + jest.resetModules();
19 +
20 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
21 + ReactFeatureFlags.warnAboutDeprecatedLifecycles = true;
22 +
23 + React = require('react');
24 + ReactDOM = require('react-dom');
25 + });
26 +
27 + // TODO (RFC #6) Merge this back into ReactComponentLifeCycles-test once
28 + // the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
29 + it('warns about deprecated unsafe lifecycles', function() {
30 + class MyComponent extends React.Component {
31 + componentWillMount() {}
32 + componentWillReceiveProps() {}
33 + componentWillUpdate() {}
34 + render() {
35 + return null;
36 + }
37 + }
38 +
39 + const container = document.createElement('div');
40 + expect(() => ReactDOM.render(<MyComponent x={1} />, container)).toWarnDev([
41 + 'Warning: MyComponent: componentWillMount() is deprecated and will be ' +
42 + 'removed in the next major version.',
43 + ]);
44 +
45 + expect(() => ReactDOM.render(<MyComponent x={2} />, container)).toWarnDev([
46 + 'Warning: MyComponent: componentWillReceiveProps() is deprecated and ' +
47 + 'will be removed in the next major version.',
48 + 'Warning: MyComponent: componentWillUpdate() is deprecated and will be ' +
49 + 'removed in the next major version.',
50 + ]);
51 +
52 + // Dedupe check (instantiate and update)
53 + ReactDOM.render(<MyComponent key="new" x={1} />, container);
54 + ReactDOM.render(<MyComponent key="new" x={2} />, container);
55 + });
56 +});
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+79 -14
@@ -165,7 +165,7 @@ describe('ReactComponentLifeCycle', () => {
165 // had provided a getInitialState method.
166 it('throws when accessing state in componentWillMount', () => {
167 class StatefulComponent extends React.Component {
168 - componentWillMount() {
168 + UNSAFE_componentWillMount() {
169 void this.state.yada;
170 }
171
@@ -182,7 +182,7 @@ describe('ReactComponentLifeCycle', () => {
182
183 it('should allow update state inside of componentWillMount', () => {
184 class StatefulComponent extends React.Component {
185 - componentWillMount() {
185 + UNSAFE_componentWillMount() {
186 this.setState({stateField: 'something'});
187 }
188
@@ -231,7 +231,7 @@ describe('ReactComponentLifeCycle', () => {
231 // reaching into the updater.
232 return this.updater.isMounted(this);
233 }
234 - componentWillMount() {
234 + UNSAFE_componentWillMount() {
235 expect(this._isMounted()).toBeFalsy();
236 }
237 componentDidMount() {
@@ -258,7 +258,7 @@ describe('ReactComponentLifeCycle', () => {
258 // reaching into the updater.
259 return this.updater.isMounted(this);
260 }
261 - componentWillMount() {
261 + UNSAFE_componentWillMount() {
262 expect(this._isMounted()).toBeFalsy();
263 }
264 componentDidMount() {
@@ -334,7 +334,7 @@ describe('ReactComponentLifeCycle', () => {
334 this.state = initState;
335 }
336
337 - componentWillMount() {
337 + UNSAFE_componentWillMount() {
338 this._testJournal.stateAtStartOfWillMount = clone(this.state);
339 this._testJournal.lifeCycleAtStartOfWillMount = getLifeCycleState(this);
340 this.state.hasWillMountCompleted = true;
@@ -509,11 +509,18 @@ describe('ReactComponentLifeCycle', () => {
509 };
510 };
511 class Outer extends React.Component {
512 - componentWillMount = logger('outer componentWillMount');
512 + state = {};
513 + static getDerivedStateFromProps(props, prevState) {
514 + log.push('outer getDerivedStateFromProps');
515 + return null;
516 + }
517 + UNSAFE_componentWillMount = logger('outer componentWillMount');
518 componentDidMount = logger('outer componentDidMount');
514 - componentWillReceiveProps = logger('outer componentWillReceiveProps');
519 + UNSAFE_componentWillReceiveProps = logger(
520 + 'outer componentWillReceiveProps',
521 + );
522 shouldComponentUpdate = logger('outer shouldComponentUpdate');
516 - componentWillUpdate = logger('outer componentWillUpdate');
523 + UNSAFE_componentWillUpdate = logger('outer componentWillUpdate');
524 componentDidUpdate = logger('outer componentDidUpdate');
525 componentWillUnmount = logger('outer componentWillUnmount');
526 render() {
@@ -526,11 +533,18 @@ describe('ReactComponentLifeCycle', () => {
533 }
534
535 class Inner extends React.Component {
529 - componentWillMount = logger('inner componentWillMount');
536 + state = {};
537 + static getDerivedStateFromProps(props, prevState) {
538 + log.push('inner getDerivedStateFromProps');
539 + return null;
540 + }
541 + UNSAFE_componentWillMount = logger('inner componentWillMount');
542 componentDidMount = logger('inner componentDidMount');
531 - componentWillReceiveProps = logger('inner componentWillReceiveProps');
543 + UNSAFE_componentWillReceiveProps = logger(
544 + 'inner componentWillReceiveProps',
545 + );
546 shouldComponentUpdate = logger('inner shouldComponentUpdate');
533 - componentWillUpdate = logger('inner componentWillUpdate');
547 + UNSAFE_componentWillUpdate = logger('inner componentWillUpdate');
548 componentDidUpdate = logger('inner componentDidUpdate');
549 componentWillUnmount = logger('inner componentWillUnmount');
550 render() {
@@ -540,21 +554,33 @@ describe('ReactComponentLifeCycle', () => {
554
555 const container = document.createElement('div');
556 log = [];
543 - ReactDOM.render(<Outer x={17} />, container);
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 + ]);
565 expect(log).toEqual([
566 + 'outer getDerivedStateFromProps',
567 'outer componentWillMount',
568 + 'inner getDerivedStateFromProps',
569 'inner componentWillMount',
570 'inner componentDidMount',
571 'outer componentDidMount',
572 ]);
573
574 + // Dedup warnings
575 log = [];
552 - ReactDOM.render(<Outer x={42} />, container);
576 + ReactDOM.render(<Outer x={2} />, container);
577 expect(log).toEqual([
578 'outer componentWillReceiveProps',
579 + 'outer getDerivedStateFromProps',
580 'outer shouldComponentUpdate',
581 'outer componentWillUpdate',
582 'inner componentWillReceiveProps',
583 + 'inner getDerivedStateFromProps',
584 'inner shouldComponentUpdate',
585 'inner componentWillUpdate',
586 'inner componentDidUpdate',
@@ -579,7 +605,7 @@ describe('ReactComponentLifeCycle', () => {
605 log.push('render');
606 return <Child />;
607 },
582 - componentWillMount() {
608 + UNSAFE_componentWillMount() {
609 log.push('will mount');
610 },
611 componentDidMount() {
@@ -619,4 +645,43 @@ describe('ReactComponentLifeCycle', () => {
645 'ref',
646 ]);
647 });
648 +
649 + it('should warn if getDerivedStateFromProps returns undefined', () => {
650 + class MyComponent extends React.Component {
651 + state = {};
652 + static getDerivedStateFromProps() {}
653 + render() {
654 + return null;
655 + }
656 + }
657 +
658 + const div = document.createElement('div');
659 + expect(() => ReactDOM.render(<MyComponent />, div)).toWarnDev(
660 + 'MyComponent.getDerivedStateFromProps(): A valid state object (or null) must ' +
661 + 'be returned. You have returned undefined.',
662 + );
663 +
664 + // De-duped
665 + ReactDOM.render(<MyComponent />, div);
666 + });
667 +
668 + it('should warn if state is not initialized before getDerivedStateFromProps', () => {
669 + class MyComponent extends React.Component {
670 + static getDerivedStateFromProps() {
671 + return null;
672 + }
673 + render() {
674 + return null;
675 + }
676 + }
677 +
678 + const div = document.createElement('div');
679 + expect(() => ReactDOM.render(<MyComponent />, div)).toWarnDev(
680 + 'MyComponent: Did not properly initialize state during construction. ' +
681 + 'Expected state to be an object, but it was undefined.',
682 + );
683 +
684 + // De-duped
685 + ReactDOM.render(<MyComponent />, div);
686 + });
687 });
packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
+10 -10
@@ -850,7 +850,7 @@ describe('ReactCompositeComponent', () => {
850 foo: PropTypes.string.isRequired,
851 };
852
853 - componentWillReceiveProps(nextProps, nextContext) {
853 + UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
854 expect('foo' in nextContext).toBe(true);
855 }
856
@@ -865,7 +865,7 @@ describe('ReactCompositeComponent', () => {
865 }
866
867 class Intermediary extends React.Component {
868 - componentWillReceiveProps(nextProps, nextContext) {
868 + UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
869 expect('foo' in nextContext).toBe(false);
870 }
871
@@ -916,7 +916,7 @@ describe('ReactCompositeComponent', () => {
916 foo: PropTypes.string.isRequired,
917 };
918
919 - componentWillReceiveProps(nextProps, nextContext) {
919 + UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
920 expect('foo' in nextContext).toBe(true);
921
922 if (nextProps !== this.props) {
@@ -938,7 +938,7 @@ describe('ReactCompositeComponent', () => {
938 foo: PropTypes.string.isRequired,
939 };
940
941 - componentWillReceiveProps(nextProps, nextContext) {
941 + UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
942 expect('foo' in nextContext).toBe(true);
943
944 if (nextProps !== this.props) {
@@ -956,7 +956,7 @@ describe('ReactCompositeComponent', () => {
956 }
957
958 class ChildWithoutContext extends React.Component {
959 - componentWillReceiveProps(nextProps, nextContext) {
959 + UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
960 expect('foo' in nextContext).toBe(false);
961
962 if (nextProps !== this.props) {
@@ -1047,7 +1047,7 @@ describe('ReactCompositeComponent', () => {
1047 class Component extends React.Component {
1048 state = {updated: false};
1049
1050 - componentWillReceiveProps(props) {
1050 + UNSAFE_componentWillReceiveProps(props) {
1051 expect(props.update).toBe(1);
1052 expect(renders).toBe(1);
1053 this.setState({updated: true});
@@ -1075,7 +1075,7 @@ describe('ReactCompositeComponent', () => {
1075 class Component extends React.Component {
1076 state = {updated: false};
1077
1078 - componentWillReceiveProps(props) {
1078 + UNSAFE_componentWillReceiveProps(props) {
1079 expect(props.update).toBe(1);
1080 expect(renders).toBe(1);
1081 this.setState({updated: true});
@@ -1377,7 +1377,7 @@ describe('ReactCompositeComponent', () => {
1377 const log = [];
1378
1379 class Spy extends React.Component {
1380 - componentWillMount() {
1380 + UNSAFE_componentWillMount() {
1381 log.push(this.props.name + ' componentWillMount');
1382 }
1383 render() {
@@ -1556,7 +1556,7 @@ describe('ReactCompositeComponent', () => {
1556 };
1557 }
1558
1559 - componentWillMount() {
1559 + UNSAFE_componentWillMount() {
1560 this.setState(
1561 {hasUpdatedState: true},
1562 () => (stateSuccessfullyUpdated = this.state.hasUpdatedState),
@@ -1586,7 +1586,7 @@ describe('ReactCompositeComponent', () => {
1586 };
1587 }
1588
1589 - componentWillMount() {
1589 + UNSAFE_componentWillMount() {
1590 instance = this;
1591 }
1592
packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js
+52 -6
@@ -46,7 +46,7 @@ describe('ReactCompositeComponent-state', () => {
46 return <div>{this.state.color}</div>;
47 }
48
49 - componentWillMount() {
49 + UNSAFE_componentWillMount() {
50 this.peekAtState('componentWillMount-start');
51 this.setState(function(state) {
52 this.peekAtState('before-setState-sunrise', state);
@@ -78,7 +78,7 @@ describe('ReactCompositeComponent-state', () => {
78 this.peekAtState('componentDidMount-end');
79 }
80
81 - componentWillReceiveProps(newProps) {
81 + UNSAFE_componentWillReceiveProps(newProps) {
82 this.peekAtState('componentWillReceiveProps-start');
83 if (newProps.nextColor) {
84 this.setState(function(state) {
@@ -105,7 +105,7 @@ describe('ReactCompositeComponent-state', () => {
105 return true;
106 }
107
108 - componentWillUpdate(nextProps, nextState) {
108 + UNSAFE_componentWillUpdate(nextProps, nextState) {
109 this.peekAtState('componentWillUpdate-currentState');
110 this.peekAtState('componentWillUpdate-nextState', nextState);
111 }
@@ -323,7 +323,7 @@ describe('ReactCompositeComponent-state', () => {
323 }
324 let updated = false;
325 class Child extends React.Component {
326 - componentWillReceiveProps() {
326 + UNSAFE_componentWillReceiveProps() {
327 if (updated) {
328 return;
329 }
@@ -383,7 +383,7 @@ describe('ReactCompositeComponent-state', () => {
383 let ops = [];
384 class Test extends React.Component {
385 state = {step: 1, extra: true};
386 - componentWillReceiveProps() {
386 + UNSAFE_componentWillReceiveProps() {
387 this.setState({step: 2}, () => {
388 // Tests that earlier setState callbacks are not dropped
389 ops.push(
@@ -426,7 +426,7 @@ describe('ReactCompositeComponent-state', () => {
426 let ops = [];
427 class Test extends React.Component {
428 state = {step: 1, extra: true};
429 - componentWillMount() {
429 + UNSAFE_componentWillMount() {
430 this.setState({step: 2}, () => {
431 // Tests that earlier setState callbacks are not dropped
432 ops.push(
@@ -458,4 +458,50 @@ describe('ReactCompositeComponent-state', () => {
458 'callback -- step: 3, extra: false',
459 ]);
460 });
461 +
462 + it('should support stateful module pattern components', () => {
463 + function Child() {
464 + return {
465 + state: {
466 + count: 123,
467 + },
468 + render() {
469 + return <div>{`count:${this.state.count}`}</div>;
470 + },
471 + };
472 + }
473 +
474 + const el = document.createElement('div');
475 + ReactDOM.render(<Child />, el);
476 +
477 + expect(el.textContent).toBe('count:123');
478 + });
479 +
480 + it('should support getDerivedStateFromProps for module pattern components', () => {
481 + function Child() {
482 + return {
483 + state: {
484 + count: 1,
485 + },
486 + render() {
487 + return <div>{`count:${this.state.count}`}</div>;
488 + },
489 + };
490 + }
491 + Child.getDerivedStateFromProps = (props, prevState) => {
492 + return {
493 + count: prevState.count + props.incrementBy,
494 + };
495 + };
496 +
497 + const el = document.createElement('div');
498 + ReactDOM.render(<Child incrementBy={0} />, el);
499 + expect(el.textContent).toBe('count:1');
500 +
501 + ReactDOM.render(<Child incrementBy={2} />, el);
502 + expect(el.textContent).toBe('count:3');
503 +
504 + ReactDOM.render(<Child incrementBy={1} />, el);
505 + expect(el.textContent).toBe('count:4');
506 + });
507 });
packages/react-dom/src/__tests__/ReactDOMServerIntegrationContext-test.js
+1 -1
@@ -231,7 +231,7 @@ describe('ReactDOMServerIntegration', () => {
231 getChildContext() {
232 return {text: this.state.text};
233 }
234 - componentWillMount() {
234 + UNSAFE_componentWillMount() {
235 this.setState({text: 'foo'});
236 }
237 render() {
packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.internal.js new
+43
@@ -0,0 +1,43 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +let React;
13 +let ReactFeatureFlags;
14 +let ReactDOMServer;
15 +
16 +describe('ReactDOMServerLifecycles', () => {
17 + beforeEach(() => {
18 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
19 + ReactFeatureFlags.warnAboutDeprecatedLifecycles = true;
20 +
21 + React = require('react');
22 + ReactDOMServer = require('react-dom/server');
23 + });
24 +
25 + // TODO (RFC #6) Merge this back into ReactDOMServerLifecycles-test once
26 + // the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
27 + it('should warn about deprecated lifecycle hooks', () => {
28 + class Component extends React.Component {
29 + componentWillMount() {}
30 + render() {
31 + return null;
32 + }
33 + }
34 +
35 + expect(() => ReactDOMServer.renderToString(<Component />)).toWarnDev(
36 + 'Warning: Component: componentWillMount() is deprecated and will be removed ' +
37 + 'in the next major version.',
38 + );
39 +
40 + // De-duped
41 + ReactDOMServer.renderToString(<Component />);
42 + });
43 +});
packages/react-dom/src/__tests__/ReactDOMServerLifecycles-test.js new
+157
@@ -0,0 +1,157 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
13 +
14 +let React;
15 +let ReactDOMServer;
16 +
17 +function initModules() {
18 + // Reset warning cache.
19 + jest.resetModuleRegistry();
20 + React = require('react');
21 + ReactDOMServer = require('react-dom/server');
22 +
23 + // Make them available to the helpers.
24 + return {
25 + ReactDOMServer,
26 + };
27 +}
28 +
29 +const {resetModules} = ReactDOMServerIntegrationUtils(initModules);
30 +
31 +describe('ReactDOMServerLifecycles', () => {
32 + beforeEach(() => {
33 + resetModules();
34 + });
35 +
36 + it('should invoke the correct lifecycle hooks', () => {
37 + const log = [];
38 +
39 + class Outer extends React.Component {
40 + UNSAFE_componentWillMount() {
41 + log.push('outer componentWillMount');
42 + }
43 + render() {
44 + log.push('outer render');
45 + return <Inner />;
46 + }
47 + }
48 +
49 + class Inner extends React.Component {
50 + UNSAFE_componentWillMount() {
51 + log.push('inner componentWillMount');
52 + }
53 + render() {
54 + log.push('inner render');
55 + return null;
56 + }
57 + }
58 +
59 + ReactDOMServer.renderToString(<Outer />);
60 + expect(log).toEqual([
61 + 'outer componentWillMount',
62 + 'outer render',
63 + 'inner componentWillMount',
64 + 'inner render',
65 + ]);
66 + });
67 +
68 + it('should update instance.state with value returned from getDerivedStateFromProps', () => {
69 + class Grandparent extends React.Component {
70 + state = {
71 + foo: 'foo',
72 + };
73 + render() {
74 + return (
75 + <div>
76 + {`Grandparent: ${this.state.foo}`}
77 + <Parent />
78 + </div>
79 + );
80 + }
81 + }
82 +
83 + class Parent extends React.Component {
84 + state = {
85 + bar: 'bar',
86 + baz: 'baz',
87 + };
88 + static getDerivedStateFromProps(props, prevState) {
89 + return {
90 + bar: `not ${prevState.bar}`,
91 + };
92 + }
93 + render() {
94 + return (
95 + <div>
96 + {`Parent: ${this.state.bar}, ${this.state.baz}`}
97 + <Child />;
98 + </div>
99 + );
100 + }
101 + }
102 +
103 + class Child extends React.Component {
104 + state = {};
105 + static getDerivedStateFromProps() {
106 + return {
107 + qux: 'qux',
108 + };
109 + }
110 + render() {
111 + return `Child: ${this.state.qux}`;
112 + }
113 + }
114 +
115 + const markup = ReactDOMServer.renderToString(<Grandparent />);
116 + expect(markup).toContain('Grandparent: foo');
117 + expect(markup).toContain('Parent: not bar, baz');
118 + expect(markup).toContain('Child: qux');
119 + });
120 +
121 + it('should warn if getDerivedStateFromProps returns undefined', () => {
122 + class Component extends React.Component {
123 + state = {};
124 + static getDerivedStateFromProps() {}
125 + render() {
126 + return null;
127 + }
128 + }
129 +
130 + expect(() => ReactDOMServer.renderToString(<Component />)).toWarnDev(
131 + 'Component.getDerivedStateFromProps(): A valid state object (or null) must ' +
132 + 'be returned. You have returned undefined.',
133 + );
134 +
135 + // De-duped
136 + ReactDOMServer.renderToString(<Component />);
137 + });
138 +
139 + it('should warn if state is not initialized before getDerivedStateFromProps', () => {
140 + class Component extends React.Component {
141 + static getDerivedStateFromProps() {
142 + return null;
143 + }
144 + render() {
145 + return null;
146 + }
147 + }
148 +
149 + expect(() => ReactDOMServer.renderToString(<Component />)).toWarnDev(
150 + 'Component: Did not properly initialize state during construction. ' +
151 + 'Expected state to be an object, but it was undefined.',
152 + );
153 +
154 + // De-duped
155 + ReactDOMServer.renderToString(<Component />);
156 + });
157 +});
packages/react-dom/src/__tests__/ReactErrorBoundaries-test.js
+38 -38
@@ -50,16 +50,16 @@ describe('ReactErrorBoundaries', () => {
50 log.push('BrokenConstructor render');
51 return <div>{this.props.children}</div>;
52 }
53 - componentWillMount() {
53 + UNSAFE_componentWillMount() {
54 log.push('BrokenConstructor componentWillMount');
55 }
56 componentDidMount() {
57 log.push('BrokenConstructor componentDidMount');
58 }
59 - componentWillReceiveProps() {
59 + UNSAFE_componentWillReceiveProps() {
60 log.push('BrokenConstructor componentWillReceiveProps');
61 }
62 - componentWillUpdate() {
62 + UNSAFE_componentWillUpdate() {
63 log.push('BrokenConstructor componentWillUpdate');
64 }
65 componentDidUpdate() {
@@ -79,17 +79,17 @@ describe('ReactErrorBoundaries', () => {
79 log.push('BrokenComponentWillMount render');
80 return <div>{this.props.children}</div>;
81 }
82 - componentWillMount() {
82 + UNSAFE_componentWillMount() {
83 log.push('BrokenComponentWillMount componentWillMount [!]');
84 throw new Error('Hello');
85 }
86 componentDidMount() {
87 log.push('BrokenComponentWillMount componentDidMount');
88 }
89 - componentWillReceiveProps() {
89 + UNSAFE_componentWillReceiveProps() {
90 log.push('BrokenComponentWillMount componentWillReceiveProps');
91 }
92 - componentWillUpdate() {
92 + UNSAFE_componentWillUpdate() {
93 log.push('BrokenComponentWillMount componentWillUpdate');
94 }
95 componentDidUpdate() {
@@ -109,17 +109,17 @@ describe('ReactErrorBoundaries', () => {
109 log.push('BrokenComponentDidMount render');
110 return <div>{this.props.children}</div>;
111 }
112 - componentWillMount() {
112 + UNSAFE_componentWillMount() {
113 log.push('BrokenComponentDidMount componentWillMount');
114 }
115 componentDidMount() {
116 log.push('BrokenComponentDidMount componentDidMount [!]');
117 throw new Error('Hello');
118 }
119 - componentWillReceiveProps() {
119 + UNSAFE_componentWillReceiveProps() {
120 log.push('BrokenComponentDidMount componentWillReceiveProps');
121 }
122 - componentWillUpdate() {
122 + UNSAFE_componentWillUpdate() {
123 log.push('BrokenComponentDidMount componentWillUpdate');
124 }
125 componentDidUpdate() {
@@ -139,19 +139,19 @@ describe('ReactErrorBoundaries', () => {
139 log.push('BrokenComponentWillReceiveProps render');
140 return <div>{this.props.children}</div>;
141 }
142 - componentWillMount() {
142 + UNSAFE_componentWillMount() {
143 log.push('BrokenComponentWillReceiveProps componentWillMount');
144 }
145 componentDidMount() {
146 log.push('BrokenComponentWillReceiveProps componentDidMount');
147 }
148 - componentWillReceiveProps() {
148 + UNSAFE_componentWillReceiveProps() {
149 log.push(
150 'BrokenComponentWillReceiveProps componentWillReceiveProps [!]',
151 );
152 throw new Error('Hello');
153 }
154 - componentWillUpdate() {
154 + UNSAFE_componentWillUpdate() {
155 log.push('BrokenComponentWillReceiveProps componentWillUpdate');
156 }
157 componentDidUpdate() {
@@ -171,16 +171,16 @@ describe('ReactErrorBoundaries', () => {
171 log.push('BrokenComponentWillUpdate render');
172 return <div>{this.props.children}</div>;
173 }
174 - componentWillMount() {
174 + UNSAFE_componentWillMount() {
175 log.push('BrokenComponentWillUpdate componentWillMount');
176 }
177 componentDidMount() {
178 log.push('BrokenComponentWillUpdate componentDidMount');
179 }
180 - componentWillReceiveProps() {
180 + UNSAFE_componentWillReceiveProps() {
181 log.push('BrokenComponentWillUpdate componentWillReceiveProps');
182 }
183 - componentWillUpdate() {
183 + UNSAFE_componentWillUpdate() {
184 log.push('BrokenComponentWillUpdate componentWillUpdate [!]');
185 throw new Error('Hello');
186 }
@@ -204,16 +204,16 @@ describe('ReactErrorBoundaries', () => {
204 log.push('BrokenComponentDidUpdate render');
205 return <div>{this.props.children}</div>;
206 }
207 - componentWillMount() {
207 + UNSAFE_componentWillMount() {
208 log.push('BrokenComponentDidUpdate componentWillMount');
209 }
210 componentDidMount() {
211 log.push('BrokenComponentDidUpdate componentDidMount');
212 }
213 - componentWillReceiveProps() {
213 + UNSAFE_componentWillReceiveProps() {
214 log.push('BrokenComponentDidUpdate componentWillReceiveProps');
215 }
216 - componentWillUpdate() {
216 + UNSAFE_componentWillUpdate() {
217 log.push('BrokenComponentDidUpdate componentWillUpdate');
218 }
219 componentDidUpdate() {
@@ -237,16 +237,16 @@ describe('ReactErrorBoundaries', () => {
237 log.push('BrokenComponentWillUnmount render');
238 return <div>{this.props.children}</div>;
239 }
240 - componentWillMount() {
240 + UNSAFE_componentWillMount() {
241 log.push('BrokenComponentWillUnmount componentWillMount');
242 }
243 componentDidMount() {
244 log.push('BrokenComponentWillUnmount componentDidMount');
245 }
246 - componentWillReceiveProps() {
246 + UNSAFE_componentWillReceiveProps() {
247 log.push('BrokenComponentWillUnmount componentWillReceiveProps');
248 }
249 - componentWillUpdate() {
249 + UNSAFE_componentWillUpdate() {
250 log.push('BrokenComponentWillUnmount componentWillUpdate');
251 }
252 componentDidUpdate() {
@@ -272,7 +272,7 @@ describe('ReactErrorBoundaries', () => {
272 log.push('BrokenComponentWillMountErrorBoundary render success');
273 return <div>{this.props.children}</div>;
274 }
275 - componentWillMount() {
275 + UNSAFE_componentWillMount() {
276 log.push(
277 'BrokenComponentWillMountErrorBoundary componentWillMount [!]',
278 );
@@ -304,7 +304,7 @@ describe('ReactErrorBoundaries', () => {
304 log.push('BrokenComponentDidMountErrorBoundary render success');
305 return <div>{this.props.children}</div>;
306 }
307 - componentWillMount() {
307 + UNSAFE_componentWillMount() {
308 log.push('BrokenComponentDidMountErrorBoundary componentWillMount');
309 }
310 componentDidMount() {
@@ -334,7 +334,7 @@ describe('ReactErrorBoundaries', () => {
334 log.push('BrokenRenderErrorBoundary render success');
335 return <div>{this.props.children}</div>;
336 }
337 - componentWillMount() {
337 + UNSAFE_componentWillMount() {
338 log.push('BrokenRenderErrorBoundary componentWillMount');
339 }
340 componentDidMount() {
@@ -358,16 +358,16 @@ describe('ReactErrorBoundaries', () => {
358 log.push('BrokenRender render [!]');
359 throw new Error('Hello');
360 }
361 - componentWillMount() {
361 + UNSAFE_componentWillMount() {
362 log.push('BrokenRender componentWillMount');
363 }
364 componentDidMount() {
365 log.push('BrokenRender componentDidMount');
366 }
367 - componentWillReceiveProps() {
367 + UNSAFE_componentWillReceiveProps() {
368 log.push('BrokenRender componentWillReceiveProps');
369 }
370 - componentWillUpdate() {
370 + UNSAFE_componentWillUpdate() {
371 log.push('BrokenRender componentWillUpdate');
372 }
373 componentDidUpdate() {
@@ -387,7 +387,7 @@ describe('ReactErrorBoundaries', () => {
387 log.push('NoopErrorBoundary render');
388 return <BrokenRender />;
389 }
390 - componentWillMount() {
390 + UNSAFE_componentWillMount() {
391 log.push('NoopErrorBoundary componentWillMount');
392 }
393 componentDidMount() {
@@ -413,16 +413,16 @@ describe('ReactErrorBoundaries', () => {
413 log.push(`${this.props.logName} render`);
414 return <div>{this.props.children}</div>;
415 }
416 - componentWillMount() {
416 + UNSAFE_componentWillMount() {
417 log.push(`${this.props.logName} componentWillMount`);
418 }
419 componentDidMount() {
420 log.push(`${this.props.logName} componentDidMount`);
421 }
422 - componentWillReceiveProps() {
422 + UNSAFE_componentWillReceiveProps() {
423 log.push(`${this.props.logName} componentWillReceiveProps`);
424 }
425 - componentWillUpdate() {
425 + UNSAFE_componentWillUpdate() {
426 log.push(`${this.props.logName} componentWillUpdate`);
427 }
428 componentDidUpdate() {
@@ -451,16 +451,16 @@ describe('ReactErrorBoundaries', () => {
451 log.push(`${this.props.logName} componentDidCatch`);
452 this.setState({error});
453 }
454 - componentWillMount() {
454 + UNSAFE_componentWillMount() {
455 log.push(`${this.props.logName} componentWillMount`);
456 }
457 componentDidMount() {
458 log.push(`${this.props.logName} componentDidMount`);
459 }
460 - componentWillReceiveProps() {
460 + UNSAFE_componentWillReceiveProps() {
461 log.push(`${this.props.logName} componentWillReceiveProps`);
462 }
463 - componentWillUpdate() {
463 + UNSAFE_componentWillUpdate() {
464 log.push(`${this.props.logName} componentWillUpdate`);
465 }
466 componentDidUpdate() {
@@ -490,7 +490,7 @@ describe('ReactErrorBoundaries', () => {
490 log.push('RetryErrorBoundary render');
491 return <BrokenRender />;
492 }
493 - componentWillMount() {
493 + UNSAFE_componentWillMount() {
494 log.push('RetryErrorBoundary componentWillMount');
495 }
496 componentDidMount() {
@@ -511,7 +511,7 @@ describe('ReactErrorBoundaries', () => {
511 super(props);
512 log.push('ErrorMessage constructor');
513 }
514 - componentWillMount() {
514 + UNSAFE_componentWillMount() {
515 log.push('ErrorMessage componentWillMount');
516 }
517 componentDidMount() {
@@ -703,7 +703,7 @@ describe('ReactErrorBoundaries', () => {
703 render() {
704 return <div>{this.props.children}</div>;
705 }
706 - componentWillMount() {
706 + UNSAFE_componentWillMount() {
707 throw new Error('Hello');
708 }
709 }
@@ -727,7 +727,7 @@ describe('ReactErrorBoundaries', () => {
727 render() {
728 return <div>{this.props.children}</div>;
729 },
730 - componentWillMount() {
730 + UNSAFE_componentWillMount() {
731 throw new Error('Hello');
732 },
733 };
packages/react-dom/src/__tests__/ReactMultiChild-test.js
+1 -1
@@ -330,7 +330,7 @@ describe('ReactMultiChild', () => {
330 const log = [];
331
332 class Spy extends React.Component {
333 - componentWillMount() {
333 + UNSAFE_componentWillMount() {
334 log.push(this.props.name + ' componentWillMount');
335 }
336 render() {
packages/react-dom/src/__tests__/ReactServerRendering-test.js
+11 -11
@@ -98,7 +98,7 @@ describe('ReactDOMServer', () => {
98 this.state = {name: 'TestComponent'};
99 }
100
101 - componentWillMount() {
101 + UNSAFE_componentWillMount() {
102 lifecycle.push('componentWillMount');
103 }
104
@@ -111,7 +111,7 @@ describe('ReactDOMServer', () => {
111 return <span>Component name: {this.state.name}</span>;
112 }
113
114 - componentWillUpdate() {
114 + UNSAFE_componentWillUpdate() {
115 lifecycle.push('componentWillUpdate');
116 }
117
@@ -123,7 +123,7 @@ describe('ReactDOMServer', () => {
123 lifecycle.push('shouldComponentUpdate');
124 }
125
126 - componentWillReceiveProps() {
126 + UNSAFE_componentWillReceiveProps() {
127 lifecycle.push('componentWillReceiveProps');
128 }
129
@@ -240,7 +240,7 @@ describe('ReactDOMServer', () => {
240 this.state = {name: 'TestComponent'};
241 }
242
243 - componentWillMount() {
243 + UNSAFE_componentWillMount() {
244 lifecycle.push('componentWillMount');
245 }
246
@@ -253,7 +253,7 @@ describe('ReactDOMServer', () => {
253 return <span>Component name: {this.state.name}</span>;
254 }
255
256 - componentWillUpdate() {
256 + UNSAFE_componentWillUpdate() {
257 lifecycle.push('componentWillUpdate');
258 }
259
@@ -265,7 +265,7 @@ describe('ReactDOMServer', () => {
265 lifecycle.push('shouldComponentUpdate');
266 }
267
268 - componentWillReceiveProps() {
268 + UNSAFE_componentWillReceiveProps() {
269 lifecycle.push('componentWillReceiveProps');
270 }
271
@@ -297,7 +297,7 @@ describe('ReactDOMServer', () => {
297
298 it('allows setState in componentWillMount without using DOM', () => {
299 class Component extends React.Component {
300 - componentWillMount() {
300 + UNSAFE_componentWillMount() {
301 this.setState({text: 'hello, world'});
302 }
303
@@ -316,7 +316,7 @@ describe('ReactDOMServer', () => {
316 this.state = {text: 'default state'};
317 }
318
319 - componentWillMount() {
319 + UNSAFE_componentWillMount() {
320 this.setState({text: 'hello, world'});
321 }
322
@@ -397,7 +397,7 @@ describe('ReactDOMServer', () => {
397 }
398
399 class Component extends React.Component {
400 - componentWillMount() {
400 + UNSAFE_componentWillMount() {
401 this.setState({text: 'hello, world'});
402 }
403
@@ -420,7 +420,7 @@ describe('ReactDOMServer', () => {
420
421 it('warns with a no-op when an async setState is triggered', () => {
422 class Foo extends React.Component {
423 - componentWillMount() {
423 + UNSAFE_componentWillMount() {
424 this.setState({text: 'hello'});
425 setTimeout(() => {
426 this.setState({text: 'error'});
@@ -446,7 +446,7 @@ describe('ReactDOMServer', () => {
446
447 it('warns with a no-op when an async forceUpdate is triggered', () => {
448 class Baz extends React.Component {
449 - componentWillMount() {
449 + UNSAFE_componentWillMount() {
450 this.forceUpdate();
451 setTimeout(() => {
452 this.forceUpdate();
packages/react-dom/src/__tests__/ReactStatelessComponent-test.js
+16
@@ -98,6 +98,22 @@ describe('ReactStatelessComponent', () => {
98 expect(el.textContent).toBe('mest');
99 });
100
101 + it('should warn for getDerivedStateFromProps on a functional component', () => {
102 + function StatelessComponentWithChildContext() {
103 + return null;
104 + }
105 + StatelessComponentWithChildContext.getDerivedStateFromProps = function() {};
106 +
107 + const container = document.createElement('div');
108 +
109 + expect(() =>
110 + ReactDOM.render(<StatelessComponentWithChildContext />, container),
111 + ).toWarnDev(
112 + 'StatelessComponentWithChildContext: Stateless ' +
113 + 'functional components do not support getDerivedStateFromProps.',
114 + );
115 + });
116 +
117 it('should warn for childContextTypes on a functional component', () => {
118 function StatelessComponentWithChildContext(props) {
119 return <div>{props.name}</div>;
packages/react-dom/src/__tests__/ReactUpdates-test.js
+9 -9
@@ -399,7 +399,7 @@ describe('ReactUpdates', () => {
399 let didUpdates = [];
400
401 const UpdateLoggingMixin = {
402 - componentWillUpdate: function() {
402 + UNSAFE_componentWillUpdate: function() {
403 willUpdates.push(this.constructor.displayName);
404 },
405 componentDidUpdate: function() {
@@ -723,7 +723,7 @@ describe('ReactUpdates', () => {
723 return <div />;
724 }
725
726 - componentWillUpdate() {
726 + UNSAFE_componentWillUpdate() {
727 x.go();
728 }
729 }
@@ -746,7 +746,7 @@ describe('ReactUpdates', () => {
746 class A extends React.Component {
747 state = {x: 0};
748
749 - componentWillMount() {
749 + UNSAFE_componentWillMount() {
750 a = this;
751 }
752
@@ -756,7 +756,7 @@ describe('ReactUpdates', () => {
756 }
757
758 class B extends React.Component {
759 - componentWillMount() {
759 + UNSAFE_componentWillMount() {
760 a.setState({x: 1});
761 }
762
@@ -784,7 +784,7 @@ describe('ReactUpdates', () => {
784 class A extends React.Component {
785 state = {x: this.props.x};
786
787 - componentWillReceiveProps(nextProps) {
787 + UNSAFE_componentWillReceiveProps(nextProps) {
788 const newX = nextProps.x;
789 this.setState({x: newX}, function() {
790 // State should have updated by the time this callback gets called
@@ -945,7 +945,7 @@ describe('ReactUpdates', () => {
945 class Child extends React.Component {
946 state = {updated: false};
947
948 - componentWillUpdate() {
948 + UNSAFE_componentWillUpdate() {
949 if (!once) {
950 once = true;
951 this.setState({updated: true});
@@ -1100,7 +1100,7 @@ describe('ReactUpdates', () => {
1100 let ops = [];
1101 class Foo extends React.Component {
1102 state = {a: false, b: false};
1103 - componentWillUpdate(_, nextState) {
1103 + UNSAFE_componentWillUpdate(_, nextState) {
1104 if (!nextState.a) {
1105 this.setState({a: true});
1106 }
@@ -1143,7 +1143,7 @@ describe('ReactUpdates', () => {
1143 let ops = [];
1144 class Foo extends React.Component {
1145 state = {a: false};
1146 - componentWillUpdate(_, nextState) {
1146 + UNSAFE_componentWillUpdate(_, nextState) {
1147 if (!nextState.a) {
1148 this.setState({a: true});
1149 }
@@ -1316,7 +1316,7 @@ describe('ReactUpdates', () => {
1316 componentDidMount() {
1317 this.setState({step: 1});
1318 }
1319 - componentWillUpdate() {
1319 + UNSAFE_componentWillUpdate() {
1320 this.setState({step: 2});
1321 }
1322 render() {
packages/react-dom/src/__tests__/findDOMNode-test.js
+1 -1
@@ -88,7 +88,7 @@ describe('findDOMNode', () => {
88
89 it('findDOMNode should not throw an error when called within a component that is not mounted', () => {
90 class Bar extends React.Component {
91 - componentWillMount() {
91 + UNSAFE_componentWillMount() {
92 expect(ReactDOM.findDOMNode(this)).toBeNull();
93 }
94
packages/react-dom/src/server/ReactPartialRenderer.js
+75 -3
@@ -19,6 +19,7 @@ import warning from 'fbjs/lib/warning';
19 import checkPropTypes from 'prop-types/checkPropTypes';
20 import describeComponentFrame from 'shared/describeComponentFrame';
21 import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState';
22 +import {warnAboutDeprecatedLifecycles} from 'shared/ReactFeatureFlags';
23 import {
24 REACT_FRAGMENT_TYPE,
25 REACT_CALL_TYPE,
@@ -123,6 +124,9 @@ let didWarnDefaultTextareaValue = false;
124 let didWarnInvalidOptionChildren = false;
125 const didWarnAboutNoopUpdateForComponent = {};
126 const didWarnAboutBadClass = {};
127 +const didWarnAboutDeprecatedWillMount = {};
128 +const didWarnAboutUndefinedDerivedState = {};
129 +const didWarnAboutUninitializedState = {};
130 const valuePropNames = ['value', 'defaultValue'];
131 const newlineEatingTags = {
132 listing: true,
@@ -421,6 +425,49 @@ function resolve(
425
426 if (shouldConstruct(Component)) {
427 inst = new Component(element.props, publicContext, updater);
428 +
429 + if (typeof Component.getDerivedStateFromProps === 'function') {
430 + if (__DEV__) {
431 + if (inst.state === null || inst.state === undefined) {
432 + const componentName = getComponentName(Component) || 'Unknown';
433 + if (!didWarnAboutUninitializedState[componentName]) {
434 + warning(
435 + false,
436 + '%s: Did not properly initialize state during construction. ' +
437 + 'Expected state to be an object, but it was %s.',
438 + componentName,
439 + inst.state === null ? 'null' : 'undefined',
440 + );
441 + didWarnAboutUninitializedState[componentName] = true;
442 + }
443 + }
444 + }
445 +
446 + partialState = Component.getDerivedStateFromProps.call(
447 + null,
448 + element.props,
449 + inst.state,
450 + );
451 +
452 + if (__DEV__) {
453 + if (partialState === undefined) {
454 + const componentName = getComponentName(Component) || 'Unknown';
455 + if (!didWarnAboutUndefinedDerivedState[componentName]) {
456 + warning(
457 + false,
458 + '%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' +
459 + 'You have returned undefined.',
460 + componentName,
461 + );
462 + didWarnAboutUndefinedDerivedState[componentName] = true;
463 + }
464 + }
465 + }
466 +
467 + if (partialState != null) {
468 + inst.state = Object.assign({}, inst.state, partialState);
469 + }
470 + }
471 } else {
472 if (__DEV__) {
473 if (
@@ -457,8 +504,33 @@ function resolve(
504 if (initialState === undefined) {
505 inst.state = initialState = null;
506 }
460 - if (inst.componentWillMount) {
461 - inst.componentWillMount();
507 + if (inst.UNSAFE_componentWillMount || inst.componentWillMount) {
508 + if (inst.componentWillMount) {
509 + if (__DEV__) {
510 + if (warnAboutDeprecatedLifecycles) {
511 + const componentName = getComponentName(Component) || 'Unknown';
512 +
513 + if (!didWarnAboutDeprecatedWillMount[componentName]) {
514 + warning(
515 + false,
516 + '%s: componentWillMount() is deprecated and will be ' +
517 + 'removed in the next major version. Read about the motivations ' +
518 + 'behind this change: ' +
519 + 'https://fb.me/react-async-component-lifecycle-hooks' +
520 + '\n\n' +
521 + 'As a temporary workaround, you can rename to ' +
522 + 'UNSAFE_componentWillMount instead.',
523 + componentName,
524 + );
525 + didWarnAboutDeprecatedWillMount[componentName] = true;
526 + }
527 + }
528 + }
529 +
530 + inst.componentWillMount();
531 + } else {
532 + inst.UNSAFE_componentWillMount();
533 + }
534 if (queue.length) {
535 oldQueue = queue;
536 oldReplace = replace;
@@ -476,7 +548,7 @@ function resolve(
548 typeof partial === 'function'
549 ? partial.call(inst, nextState, element.props, publicContext)
550 : partial;
479 - if (partialState) {
551 + if (partialState != null) {
552 if (dontMutate) {
553 dontMutate = false;
554 nextState = Object.assign({}, nextState, partialState);
packages/react-native-renderer/src/NativeMethodsMixin.js
+9 -1
@@ -187,7 +187,9 @@ if (__DEV__) {
187 const NativeMethodsMixin_DEV = (NativeMethodsMixin: any);
188 invariant(
189 !NativeMethodsMixin_DEV.componentWillMount &&
190 - !NativeMethodsMixin_DEV.componentWillReceiveProps,
190 + !NativeMethodsMixin_DEV.componentWillReceiveProps &&
191 + !NativeMethodsMixin_DEV.UNSAFE_componentWillMount &&
192 + !NativeMethodsMixin_DEV.UNSAFE_componentWillReceiveProps,
193 'Do not override existing functions.',
194 );
195 NativeMethodsMixin_DEV.componentWillMount = function() {
@@ -196,6 +198,12 @@ if (__DEV__) {
198 NativeMethodsMixin_DEV.componentWillReceiveProps = function(newProps) {
199 throwOnStylesProp(this, newProps);
200 };
201 + NativeMethodsMixin_DEV.UNSAFE_componentWillMount = function() {
202 + throwOnStylesProp(this, this.props);
203 + };
204 + NativeMethodsMixin_DEV.UNSAFE_componentWillReceiveProps = function(newProps) {
205 + throwOnStylesProp(this, newProps);
206 + };
207 }
208
209 export default NativeMethodsMixin;
packages/react-reconciler/src/ReactFiberBeginWork.js
+45 -4
@@ -60,12 +60,14 @@ import {
60 import {NoWork, Never} from './ReactFiberExpirationTime';
61 import {AsyncUpdates} from './ReactTypeOfInternalContext';
62
63 -let warnedAboutStatelessRefs;
63 let didWarnAboutBadClass;
64 +let didWarnAboutGetDerivedStateOnFunctionalComponent;
65 +let didWarnAboutStatelessRefs;
66
67 if (__DEV__) {
67 - warnedAboutStatelessRefs = {};
68 didWarnAboutBadClass = {};
69 + didWarnAboutGetDerivedStateOnFunctionalComponent = {};
70 + didWarnAboutStatelessRefs = {};
71 }
72
73 export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
@@ -87,6 +89,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
89
90 const {
91 adoptClassInstance,
92 + callGetDerivedStateFromProps,
93 constructClassInstance,
94 mountClassInstance,
95 // resumeMountClassInstance,
@@ -483,9 +486,30 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
486 value !== null &&
487 typeof value.render === 'function'
488 ) {
489 + const Component = workInProgress.type;
490 +
491 // Proceed under the assumption that this is a class instance
492 workInProgress.tag = ClassComponent;
493
494 + workInProgress.memoizedState =
495 + value.state !== null && value.state !== undefined ? value.state : null;
496 +
497 + if (typeof Component.getDerivedStateFromProps === 'function') {
498 + const partialState = callGetDerivedStateFromProps(
499 + workInProgress,
500 + value,
501 + props,
502 + );
503 +
504 + if (partialState !== null && partialState !== undefined) {
505 + workInProgress.memoizedState = Object.assign(
506 + {},
507 + workInProgress.memoizedState,
508 + partialState,
509 + );
510 + }
511 + }
512 +
513 // Push context providers early to prevent context stack mismatches.
514 // During mounting we don't know the child context yet as the instance doesn't exist.
515 // We will invalidate the child context in finishClassComponent() right after rendering.
@@ -518,8 +542,8 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
542 if (debugSource) {
543 warningKey = debugSource.fileName + ':' + debugSource.lineNumber;
544 }
521 - if (!warnedAboutStatelessRefs[warningKey]) {
522 - warnedAboutStatelessRefs[warningKey] = true;
545 + if (!didWarnAboutStatelessRefs[warningKey]) {
546 + didWarnAboutStatelessRefs[warningKey] = true;
547 warning(
548 false,
549 'Stateless function components cannot be given refs. ' +
@@ -529,6 +553,23 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
553 );
554 }
555 }
556 +
557 + if (typeof fn.getDerivedStateFromProps === 'function') {
558 + const componentName = getComponentName(workInProgress) || 'Unknown';
559 +
560 + if (
561 + !didWarnAboutGetDerivedStateOnFunctionalComponent[componentName]
562 + ) {
563 + warning(
564 + false,
565 + '%s: Stateless functional components do not support getDerivedStateFromProps.',
566 + componentName,
567 + );
568 + didWarnAboutGetDerivedStateOnFunctionalComponent[
569 + componentName
570 + ] = true;
571 + }
572 + }
573 }
574 reconcileChildren(current, workInProgress, value);
575 memoizeProps(workInProgress, props);
packages/react-reconciler/src/ReactFiberClassComponent.js
+241 -18
@@ -14,6 +14,7 @@ import {Update} from 'shared/ReactTypeOfSideEffect';
14 import {
15 debugRenderPhaseSideEffects,
16 enableAsyncSubtreeAPI,
17 + warnAboutDeprecatedLifecycles,
18 } from 'shared/ReactFeatureFlags';
19 import {isMounted} from 'react-reconciler/reflection';
20 import * as ReactInstanceMap from 'shared/ReactInstanceMap';
@@ -40,12 +41,27 @@ import {hasContextChanged} from './ReactFiberContext';
41 const fakeInternalInstance = {};
42 const isArray = Array.isArray;
43
44 +let didWarnAboutLegacyWillMount;
45 +let didWarnAboutLegacyWillReceiveProps;
46 +let didWarnAboutLegacyWillUpdate;
47 let didWarnAboutStateAssignmentForComponent;
48 +let didWarnAboutUndefinedDerivedState;
49 +let didWarnAboutUninitializedState;
50 +let didWarnAboutWillReceivePropsAndDerivedState;
51 let warnOnInvalidCallback;
52
53 if (__DEV__) {
47 - const didWarnOnInvalidCallback = {};
54 + if (warnAboutDeprecatedLifecycles) {
55 + didWarnAboutLegacyWillMount = {};
56 + didWarnAboutLegacyWillReceiveProps = {};
57 + didWarnAboutLegacyWillUpdate = {};
58 + }
59 didWarnAboutStateAssignmentForComponent = {};
60 + didWarnAboutUndefinedDerivedState = {};
61 + didWarnAboutUninitializedState = {};
62 + didWarnAboutWillReceivePropsAndDerivedState = {};
63 +
64 + const didWarnOnInvalidCallback = {};
65
66 warnOnInvalidCallback = function(callback: mixed, callerName: string) {
67 if (callback === null || typeof callback === 'function') {
@@ -319,6 +335,14 @@ export default function(
335 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
336 name,
337 );
338 + const noUnsafeComponentWillRecieveProps =
339 + typeof instance.UNSAFE_componentWillRecieveProps !== 'function';
340 + warning(
341 + noUnsafeComponentWillRecieveProps,
342 + '%s has a method called ' +
343 + 'UNSAFE_componentWillRecieveProps(). Did you mean UNSAFE_componentWillReceiveProps()?',
344 + name,
345 + );
346 const hasMutatedProps = instance.props !== workInProgress.pendingProps;
347 warning(
348 instance.props === undefined || !hasMutatedProps,
@@ -378,8 +402,50 @@ export default function(
402 ? getMaskedContext(workInProgress, unmaskedContext)
403 : emptyObject;
404 const instance = new ctor(props, context);
405 + const state =
406 + instance.state !== null && instance.state !== undefined
407 + ? instance.state
408 + : null;
409 adoptClassInstance(workInProgress, instance);
410
411 + if (__DEV__) {
412 + if (
413 + typeof ctor.getDerivedStateFromProps === 'function' &&
414 + state === null
415 + ) {
416 + const componentName = getComponentName(workInProgress) || 'Unknown';
417 + if (!didWarnAboutUninitializedState[componentName]) {
418 + warning(
419 + false,
420 + '%s: Did not properly initialize state during construction. ' +
421 + 'Expected state to be an object, but it was %s.',
422 + componentName,
423 + instance.state === null ? 'null' : 'undefined',
424 + );
425 + didWarnAboutUninitializedState[componentName] = true;
426 + }
427 + }
428 + }
429 +
430 + workInProgress.memoizedState = state;
431 +
432 + const partialState = callGetDerivedStateFromProps(
433 + workInProgress,
434 + instance,
435 + props,
436 + );
437 +
438 + if (partialState !== null && partialState !== undefined) {
439 + // Render-phase updates (like this) should not be added to the update queue,
440 + // So that multiple render passes do not enqueue multiple updates.
441 + // Instead, just synchronously merge the returned state into the instance.
442 + workInProgress.memoizedState = Object.assign(
443 + {},
444 + workInProgress.memoizedState,
445 + partialState,
446 + );
447 + }
448 +
449 // Cache unmasked context so we can avoid recreating masked context unless necessary.
450 // ReactFiberContext usually updates this cache but can't for newly-created instances.
451 if (needsContext) {
@@ -392,7 +458,32 @@ export default function(
458 function callComponentWillMount(workInProgress, instance) {
459 startPhaseTimer(workInProgress, 'componentWillMount');
460 const oldState = instance.state;
395 - instance.componentWillMount();
461 +
462 + if (typeof instance.componentWillMount === 'function') {
463 + if (__DEV__) {
464 + if (warnAboutDeprecatedLifecycles) {
465 + const componentName = getComponentName(workInProgress) || 'Component';
466 + if (!didWarnAboutLegacyWillMount[componentName]) {
467 + warning(
468 + false,
469 + '%s: componentWillMount() is deprecated and will be ' +
470 + 'removed in the next major version. Read about the motivations ' +
471 + 'behind this change: ' +
472 + 'https://fb.me/react-async-component-lifecycle-hooks' +
473 + '\n\n' +
474 + 'As a temporary workaround, you can rename to ' +
475 + 'UNSAFE_componentWillMount instead.',
476 + componentName,
477 + );
478 + didWarnAboutLegacyWillMount[componentName] = true;
479 + }
480 + }
481 + }
482 + instance.componentWillMount();
483 + } else {
484 + instance.UNSAFE_componentWillMount();
485 + }
486 +
487 stopPhaseTimer();
488
489 if (oldState !== instance.state) {
@@ -415,14 +506,41 @@ export default function(
506 newProps,
507 newContext,
508 ) {
418 - startPhaseTimer(workInProgress, 'componentWillReceiveProps');
509 const oldState = instance.state;
420 - instance.componentWillReceiveProps(newProps, newContext);
421 - stopPhaseTimer();
510 + if (typeof instance.componentWillReceiveProps === 'function') {
511 + if (__DEV__) {
512 + if (warnAboutDeprecatedLifecycles) {
513 + const componentName = getComponentName(workInProgress) || 'Component';
514 + if (!didWarnAboutLegacyWillReceiveProps[componentName]) {
515 + warning(
516 + false,
517 + '%s: componentWillReceiveProps() is deprecated and ' +
518 + 'will be removed in the next major version. Use ' +
519 + 'static getDerivedStateFromProps() instead. Read about the ' +
520 + 'motivations behind this change: ' +
521 + 'https://fb.me/react-async-component-lifecycle-hooks' +
522 + '\n\n' +
523 + 'As a temporary workaround, you can rename to ' +
524 + 'UNSAFE_componentWillReceiveProps instead.',
525 + componentName,
526 + );
527 + didWarnAboutLegacyWillReceiveProps[componentName] = true;
528 + }
529 + }
530 + }
531
423 - // Simulate an async bailout/interruption by invoking lifecycle twice.
424 - if (debugRenderPhaseSideEffects) {
532 + startPhaseTimer(workInProgress, 'componentWillReceiveProps');
533 instance.componentWillReceiveProps(newProps, newContext);
534 + stopPhaseTimer();
535 + } else {
536 + startPhaseTimer(workInProgress, 'componentWillReceiveProps');
537 + instance.UNSAFE_componentWillReceiveProps(newProps, newContext);
538 + stopPhaseTimer();
539 +
540 + // Simulate an async bailout/interruption by invoking lifecycle twice.
541 + if (debugRenderPhaseSideEffects) {
542 + instance.UNSAFE_componentWillReceiveProps(newProps, newContext);
543 + }
544 }
545
546 if (instance.state !== oldState) {
@@ -443,6 +561,58 @@ export default function(
561 }
562 }
563
564 + function callGetDerivedStateFromProps(
565 + workInProgress: Fiber,
566 + instance: any,
567 + props: any,
568 + ) {
569 + const {type} = workInProgress;
570 +
571 + if (typeof type.getDerivedStateFromProps === 'function') {
572 + if (__DEV__) {
573 + if (
574 + typeof instance.componentWillReceiveProps === 'function' ||
575 + typeof instance.UNSAFE_componentWillReceiveProps === 'function'
576 + ) {
577 + const componentName = getComponentName(workInProgress) || 'Unknown';
578 + if (!didWarnAboutWillReceivePropsAndDerivedState[componentName]) {
579 + warning(
580 + false,
581 + '%s: Defines both componentWillReceiveProps() and static ' +
582 + 'getDerivedStateFromProps() methods. We recommend using ' +
583 + 'only getDerivedStateFromProps().',
584 + componentName,
585 + );
586 + didWarnAboutWillReceivePropsAndDerivedState[componentName] = true;
587 + }
588 + }
589 + }
590 +
591 + const partialState = type.getDerivedStateFromProps.call(
592 + null,
593 + props,
594 + workInProgress.memoizedState,
595 + );
596 +
597 + if (__DEV__) {
598 + if (partialState === undefined) {
599 + const componentName = getComponentName(workInProgress) || 'Unknown';
600 + if (!didWarnAboutUndefinedDerivedState[componentName]) {
601 + warning(
602 + false,
603 + '%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' +
604 + 'You have returned undefined.',
605 + componentName,
606 + );
607 + didWarnAboutUndefinedDerivedState[componentName] = componentName;
608 + }
609 + }
610 + }
611 +
612 + return partialState;
613 + }
614 + }
615 +
616 // Invokes the mount life-cycles on a previously never rendered instance.
617 function mountClassInstance(
618 workInProgress: Fiber,
@@ -455,12 +625,11 @@ export default function(
625 }
626
627 const instance = workInProgress.stateNode;
458 - const state = instance.state || null;
628 const props = workInProgress.pendingProps;
629 const unmaskedContext = getUnmaskedContext(workInProgress);
630
631 instance.props = props;
463 - instance.state = workInProgress.memoizedState = state;
632 + instance.state = workInProgress.memoizedState;
633 instance.refs = emptyObject;
634 instance.context = getMaskedContext(workInProgress, unmaskedContext);
635
@@ -473,7 +642,10 @@ export default function(
642 workInProgress.internalContextTag |= AsyncUpdates;
643 }
644
476 - if (typeof instance.componentWillMount === 'function') {
645 + if (
646 + typeof instance.UNSAFE_componentWillMount === 'function' ||
647 + typeof instance.componentWillMount === 'function'
648 + ) {
649 callComponentWillMount(workInProgress, instance);
650 // If we had additional state updates during this life-cycle, let's
651 // process them now.
@@ -619,7 +791,8 @@ export default function(
791 // during componentDidUpdate we pass the "current" props.
792
793 if (
622 - typeof instance.componentWillReceiveProps === 'function' &&
794 + (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
795 + typeof instance.componentWillReceiveProps === 'function') &&
796 (oldProps !== newProps || oldContext !== newContext)
797 ) {
798 callComponentWillReceiveProps(
@@ -630,6 +803,15 @@ export default function(
803 );
804 }
805
806 + let partialState;
807 + if (oldProps !== newProps) {
808 + partialState = callGetDerivedStateFromProps(
809 + workInProgress,
810 + instance,
811 + newProps,
812 + );
813 + }
814 +
815 // Compute the next state using the memoized state and the update queue.
816 const oldState = workInProgress.memoizedState;
817 // TODO: Previous state can be null.
@@ -647,6 +829,16 @@ export default function(
829 newState = oldState;
830 }
831
832 + if (partialState !== null && partialState !== undefined) {
833 + // Render-phase updates (like this) should not be added to the update queue,
834 + // So that multiple render passes do not enqueue multiple updates.
835 + // Instead, just synchronously merge the returned state into the instance.
836 + newState =
837 + newState === null || newState === undefined
838 + ? partialState
839 + : Object.assign({}, newState, partialState);
840 + }
841 +
842 if (
843 oldProps === newProps &&
844 oldState === newState &&
@@ -679,14 +871,44 @@ export default function(
871 );
872
873 if (shouldUpdate) {
682 - if (typeof instance.componentWillUpdate === 'function') {
683 - startPhaseTimer(workInProgress, 'componentWillUpdate');
684 - instance.componentWillUpdate(newProps, newState, newContext);
685 - stopPhaseTimer();
686 -
687 - // Simulate an async bailout/interruption by invoking lifecycle twice.
688 - if (debugRenderPhaseSideEffects) {
874 + if (
875 + typeof instance.UNSAFE_componentWillUpdate === 'function' ||
876 + typeof instance.componentWillUpdate === 'function'
877 + ) {
878 + if (typeof instance.componentWillUpdate === 'function') {
879 + if (__DEV__) {
880 + if (warnAboutDeprecatedLifecycles) {
881 + const componentName =
882 + getComponentName(workInProgress) || 'Component';
883 + if (!didWarnAboutLegacyWillUpdate[componentName]) {
884 + warning(
885 + false,
886 + '%s: componentWillUpdate() is deprecated and will be ' +
887 + 'removed in the next major version. Read about the motivations ' +
888 + 'behind this change: ' +
889 + 'https://fb.me/react-async-component-lifecycle-hooks' +
890 + '\n\n' +
891 + 'As a temporary workaround, you can rename to ' +
892 + 'UNSAFE_componentWillUpdate instead.',
893 + componentName,
894 + );
895 + didWarnAboutLegacyWillUpdate[componentName] = true;
896 + }
897 + }
898 + }
899 +
900 + startPhaseTimer(workInProgress, 'componentWillUpdate');
901 instance.componentWillUpdate(newProps, newState, newContext);
902 + stopPhaseTimer();
903 + } else {
904 + startPhaseTimer(workInProgress, 'componentWillUpdate');
905 + instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
906 + stopPhaseTimer();
907 +
908 + // Simulate an async bailout/interruption by invoking lifecycle twice.
909 + if (debugRenderPhaseSideEffects) {
910 + instance.UNSAFE_componentWillUpdate(newProps, newState, newContext);
911 + }
912 }
913 }
914 if (typeof instance.componentDidUpdate === 'function') {
@@ -721,6 +943,7 @@ export default function(
943
944 return {
945 adoptClassInstance,
946 + callGetDerivedStateFromProps,
947 constructClassInstance,
948 mountClassInstance,
949 // resumeMountClassInstance,
packages/react-reconciler/src/__tests__/ReactIncremental-test.js
+55 -17
@@ -571,16 +571,16 @@ describe('ReactIncremental', () => {
571 ops.push('constructor: ' + props.prop);
572 constructorCount++;
573 }
574 - componentWillMount() {
574 + UNSAFE_componentWillMount() {
575 ops.push('componentWillMount: ' + this.props.prop);
576 }
577 - componentWillReceiveProps() {
577 + UNSAFE_componentWillReceiveProps() {
578 ops.push('componentWillReceiveProps: ' + this.props.prop);
579 }
580 componentDidMount() {
581 ops.push('componentDidMount: ' + this.props.prop);
582 }
583 - componentWillUpdate() {
583 + UNSAFE_componentWillUpdate() {
584 ops.push('componentWillUpdate: ' + this.props.prop);
585 }
586 componentDidUpdate() {
@@ -1232,13 +1232,13 @@ describe('ReactIncremental', () => {
1232
1233 class LifeCycle extends React.Component {
1234 state = {x: this.props.x};
1235 - componentWillReceiveProps(nextProps) {
1235 + UNSAFE_componentWillReceiveProps(nextProps) {
1236 ops.push(
1237 'componentWillReceiveProps:' + this.state.x + '-' + nextProps.x,
1238 );
1239 this.setState({x: nextProps.x});
1240 }
1241 - componentWillMount() {
1241 + UNSAFE_componentWillMount() {
1242 ops.push('componentWillMount:' + this.state.x + '-' + this.props.x);
1243 }
1244 componentDidMount() {
@@ -1291,7 +1291,7 @@ describe('ReactIncremental', () => {
1291 super(props);
1292 this.state = {x: this.props.x + '(ctor)'};
1293 }
1294 - componentWillMount() {
1294 + UNSAFE_componentWillMount() {
1295 ops.push('componentWillMount:' + this.state.x);
1296 this.setState({x: this.props.x + '(willMount)'});
1297 }
@@ -1334,13 +1334,13 @@ describe('ReactIncremental', () => {
1334 let ops = [];
1335
1336 class LifeCycle extends React.Component {
1337 - componentWillMount() {
1337 + UNSAFE_componentWillMount() {
1338 ops.push('componentWillMount:' + this.props.x);
1339 }
1340 componentDidMount() {
1341 ops.push('componentDidMount:' + this.props.x);
1342 }
1343 - componentWillReceiveProps(nextProps) {
1343 + UNSAFE_componentWillReceiveProps(nextProps) {
1344 ops.push(
1345 'componentWillReceiveProps:' + this.props.x + '-' + nextProps.x,
1346 );
@@ -1349,7 +1349,7 @@ describe('ReactIncremental', () => {
1349 ops.push('shouldComponentUpdate:' + this.props.x + '-' + nextProps.x);
1350 return true;
1351 }
1352 - componentWillUpdate(nextProps) {
1352 + UNSAFE_componentWillUpdate(nextProps) {
1353 ops.push('componentWillUpdate:' + this.props.x + '-' + nextProps.x);
1354 }
1355 componentDidUpdate(prevProps) {
@@ -1417,6 +1417,44 @@ describe('ReactIncremental', () => {
1417 ]);
1418 });
1419
1420 + it('does not call static getDerivedStateFromProps for state-only updates', () => {
1421 + let ops = [];
1422 + let instance;
1423 +
1424 + class LifeCycle extends React.Component {
1425 + state = {};
1426 + static getDerivedStateFromProps(props, prevState) {
1427 + ops.push('getDerivedStateFromProps');
1428 + return {foo: 'foo'};
1429 + }
1430 + changeState() {
1431 + this.setState({foo: 'bar'});
1432 + }
1433 + componentWillUpdate() {
1434 + ops.push('componentWillUpdate');
1435 + }
1436 + render() {
1437 + ops.push('render');
1438 + instance = this;
1439 + return null;
1440 + }
1441 + }
1442 +
1443 + ReactNoop.render(<LifeCycle />);
1444 + ReactNoop.flush();
1445 +
1446 + expect(ops).toEqual(['getDerivedStateFromProps', 'render']);
1447 + expect(instance.state).toEqual({foo: 'foo'});
1448 +
1449 + ops = [];
1450 +
1451 + instance.changeState();
1452 + ReactNoop.flush();
1453 +
1454 + expect(ops).toEqual(['componentWillUpdate', 'render']);
1455 + expect(instance.state).toEqual({foo: 'bar'});
1456 + });
1457 +
1458 xit('does not call componentWillReceiveProps for state-only updates', () => {
1459 let ops = [];
1460
@@ -1429,21 +1467,21 @@ describe('ReactIncremental', () => {
1467 x: this.state.x + 1,
1468 });
1469 }
1432 - componentWillMount() {
1470 + UNSAFE_componentWillMount() {
1471 instances.push(this);
1472 ops.push('componentWillMount:' + this.state.x);
1473 }
1474 componentDidMount() {
1475 ops.push('componentDidMount:' + this.state.x);
1476 }
1439 - componentWillReceiveProps(nextProps) {
1477 + UNSAFE_componentWillReceiveProps(nextProps) {
1478 ops.push('componentWillReceiveProps');
1479 }
1480 shouldComponentUpdate(nextProps, nextState) {
1481 ops.push('shouldComponentUpdate:' + this.state.x + '-' + nextState.x);
1482 return true;
1483 }
1446 - componentWillUpdate(nextProps, nextState) {
1484 + UNSAFE_componentWillUpdate(nextProps, nextState) {
1485 ops.push('componentWillUpdate:' + this.state.x + '-' + nextState.x);
1486 }
1487 componentDidUpdate(prevProps, prevState) {
@@ -1460,7 +1498,7 @@ describe('ReactIncremental', () => {
1498 // output unless it fully completed.
1499 class Wrap extends React.Component {
1500 state = {y: 0};
1463 - componentWillMount() {
1501 + UNSAFE_componentWillMount() {
1502 instances.push(this);
1503 }
1504 tick() {
@@ -1570,13 +1608,13 @@ describe('ReactIncremental', () => {
1608 let ops = [];
1609
1610 class LifeCycle extends React.Component {
1573 - componentWillMount() {
1611 + UNSAFE_componentWillMount() {
1612 ops.push('componentWillMount');
1613 }
1614 componentDidMount() {
1615 ops.push('componentDidMount');
1616 }
1579 - componentWillReceiveProps(nextProps) {
1617 + UNSAFE_componentWillReceiveProps(nextProps) {
1618 ops.push('componentWillReceiveProps');
1619 }
1620 shouldComponentUpdate(nextProps) {
@@ -1584,7 +1622,7 @@ describe('ReactIncremental', () => {
1622 // Bail
1623 return this.props.x !== nextProps.x;
1624 }
1587 - componentWillUpdate(nextProps) {
1625 + UNSAFE_componentWillUpdate(nextProps) {
1626 ops.push('componentWillUpdate');
1627 }
1628 componentDidUpdate(prevProps) {
@@ -2282,7 +2320,7 @@ describe('ReactIncremental', () => {
2320 this.setState({setStateInCDU: false});
2321 }
2322 }
2285 - componentWillReceiveProps(nextProps) {
2323 + UNSAFE_componentWillReceiveProps(nextProps) {
2324 ops.push('componentWillReceiveProps');
2325 this.setState({setStateInCDU: true});
2326 }
packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.internal.js
+1 -1
@@ -26,7 +26,7 @@ describe('ReactIncrementalErrorLogging', () => {
26 spyOnProd(console, 'error');
27
28 class ErrorThrowingComponent extends React.Component {
29 - componentWillMount() {
29 + UNSAFE_componentWillMount() {
30 const error = new Error('componentWillMount error');
31 // Note: it's `true` on the Error prototype our test environment.
32 // That lets us avoid asserting on warnings for each expected error.
packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
+5 -5
@@ -237,10 +237,10 @@ describe('ReactDebugFiberPerf', () => {
237
238 it('does not treat setState from cWM or cWRP as cascading', () => {
239 class NotCascading extends React.Component {
240 - componentWillMount() {
240 + UNSAFE_componentWillMount() {
241 this.setState({});
242 }
243 - componentWillReceiveProps() {
243 + UNSAFE_componentWillReceiveProps() {
244 this.setState({});
245 }
246 render() {
@@ -276,10 +276,10 @@ describe('ReactDebugFiberPerf', () => {
276 getChildContext() {
277 return {foo: 42};
278 }
279 - componentWillMount() {}
279 + UNSAFE_componentWillMount() {}
280 componentDidMount() {}
281 - componentWillReceiveProps() {}
282 - componentWillUpdate() {}
281 + UNSAFE_componentWillReceiveProps() {}
282 + UNSAFE_componentWillUpdate() {}
283 componentDidUpdate() {}
284 componentWillUnmount() {}
285 render() {
packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.js
+4 -4
@@ -31,7 +31,7 @@ describe('ReactIncrementalReflection', () => {
31 // reaching into the updater.
32 return this.updater.isMounted(this);
33 }
34 - componentWillMount() {
34 + UNSAFE_componentWillMount() {
35 instances.push(this);
36 ops.push('componentWillMount', this._isMounted());
37 }
@@ -75,7 +75,7 @@ describe('ReactIncrementalReflection', () => {
75 _isMounted() {
76 return this.updater.isMounted(this);
77 }
78 - componentWillMount() {
78 + UNSAFE_componentWillMount() {
79 instances.push(this);
80 }
81 componentWillUnmount() {
@@ -128,14 +128,14 @@ describe('ReactIncrementalReflection', () => {
128 let classInstance = null;
129
130 class Component extends React.Component {
131 - componentWillMount() {
131 + UNSAFE_componentWillMount() {
132 classInstance = this;
133 ops.push('componentWillMount', ReactNoop.findInstance(this));
134 }
135 componentDidMount() {
136 ops.push('componentDidMount', ReactNoop.findInstance(this));
137 }
138 - componentWillUpdate() {
138 + UNSAFE_componentWillUpdate() {
139 ops.push('componentWillUpdate', ReactNoop.findInstance(this));
140 }
141 componentDidUpdate() {
packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.js
+1 -1
@@ -331,7 +331,7 @@ describe('ReactIncrementalScheduling', () => {
331 it('updates do not schedule a new callback if already inside a callback', () => {
332 class Foo extends React.Component {
333 state = {foo: 'foo'};
334 - componentWillReceiveProps() {
334 + UNSAFE_componentWillReceiveProps() {
335 ReactNoop.yield(
336 'has callback before setState: ' + ReactNoop.hasScheduledCallback(),
337 );
packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+1 -1
@@ -296,7 +296,7 @@ describe('ReactIncrementalUpdates', () => {
296
297 class Foo extends React.Component {
298 state = {};
299 - componentWillReceiveProps() {
299 + UNSAFE_componentWillReceiveProps() {
300 ops.push('componentWillReceiveProps');
301 this.setState({b: 'b'});
302 }
packages/react-test-renderer/src/ReactShallowRenderer.js
+186 -12
@@ -7,12 +7,32 @@
7 */
8
9 import React from 'react';
10 +import {warnAboutDeprecatedLifecycles} from 'shared/ReactFeatureFlags';
11 import describeComponentFrame from 'shared/describeComponentFrame';
12 import getComponentName from 'shared/getComponentName';
13 import emptyObject from 'fbjs/lib/emptyObject';
14 import invariant from 'fbjs/lib/invariant';
15 import shallowEqual from 'fbjs/lib/shallowEqual';
16 import checkPropTypes from 'prop-types/checkPropTypes';
17 +import warning from 'fbjs/lib/warning';
18 +
19 +let didWarnAboutLegacyWillMount;
20 +let didWarnAboutLegacyWillReceiveProps;
21 +let didWarnAboutLegacyWillUpdate;
22 +let didWarnAboutUndefinedDerivedState;
23 +let didWarnAboutUninitializedState;
24 +let didWarnAboutWillReceivePropsAndDerivedState;
25 +
26 +if (__DEV__) {
27 + if (warnAboutDeprecatedLifecycles) {
28 + didWarnAboutLegacyWillMount = {};
29 + didWarnAboutLegacyWillReceiveProps = {};
30 + didWarnAboutLegacyWillUpdate = {};
31 + }
32 + didWarnAboutUndefinedDerivedState = {};
33 + didWarnAboutUninitializedState = {};
34 + didWarnAboutWillReceivePropsAndDerivedState = {};
35 +}
36
37 class ReactShallowRenderer {
38 static createRenderer = function() {
@@ -73,7 +93,7 @@ class ReactShallowRenderer {
93 this._context = getMaskedContext(element.type.contextTypes, context);
94
95 if (this._instance) {
76 - this._updateClassComponent(element.type, element.props, this._context);
96 + this._updateClassComponent(element, this._context);
97 } else {
98 if (shouldConstruct(element.type)) {
99 this._instance = new element.type(
@@ -82,6 +102,30 @@ class ReactShallowRenderer {
102 this._updater,
103 );
104
105 + if (__DEV__) {
106 + if (typeof element.type.getDerivedStateFromProps === 'function') {
107 + if (
108 + this._instance.state === null ||
109 + this._instance.state === undefined
110 + ) {
111 + const componentName =
112 + getName(element.type, this._instance) || 'Unknown';
113 + if (!didWarnAboutUninitializedState[componentName]) {
114 + warning(
115 + false,
116 + '%s: Did not properly initialize state during construction. ' +
117 + 'Expected state to be an object, but it was %s.',
118 + componentName,
119 + this._instance.state === null ? 'null' : 'undefined',
120 + );
121 + didWarnAboutUninitializedState[componentName] = true;
122 + }
123 + }
124 + }
125 + }
126 +
127 + this._updateStateFromStaticLifecycle(element.props);
128 +
129 if (element.type.hasOwnProperty('contextTypes')) {
130 currentlyValidatingElement = element;
131
@@ -96,7 +140,7 @@ class ReactShallowRenderer {
140 currentlyValidatingElement = null;
141 }
142
99 - this._mountClassComponent(element.props, this._context);
143 + this._mountClassComponent(element, this._context);
144 } else {
145 this._rendered = element.type(element.props, this._context);
146 }
@@ -122,16 +166,42 @@ class ReactShallowRenderer {
166 this._instance = null;
167 }
168
125 - _mountClassComponent(props, context) {
169 + _mountClassComponent(element, context) {
170 this._instance.context = context;
127 - this._instance.props = props;
171 + this._instance.props = element.props;
172 this._instance.state = this._instance.state || null;
173 this._instance.updater = this._updater;
174
131 - if (typeof this._instance.componentWillMount === 'function') {
175 + if (
176 + typeof this._instance.UNSAFE_componentWillMount === 'function' ||
177 + typeof this._instance.componentWillMount === 'function'
178 + ) {
179 const beforeState = this._newState;
180
134 - this._instance.componentWillMount();
181 + if (typeof this._instance.componentWillMount === 'function') {
182 + if (__DEV__) {
183 + if (warnAboutDeprecatedLifecycles) {
184 + const componentName = getName(element.type, this._instance);
185 + if (!didWarnAboutLegacyWillMount[componentName]) {
186 + warning(
187 + false,
188 + '%s: componentWillMount() is deprecated and will be ' +
189 + 'removed in the next major version. Read about the motivations ' +
190 + 'behind this change: ' +
191 + 'https://fb.me/react-async-component-lifecycle-hooks' +
192 + '\n\n' +
193 + 'As a temporary workaround, you can rename to ' +
194 + 'UNSAFE_componentWillMount instead.',
195 + componentName,
196 + );
197 + didWarnAboutLegacyWillMount[componentName] = true;
198 + }
199 + }
200 + }
201 + this._instance.componentWillMount();
202 + } else {
203 + this._instance.UNSAFE_componentWillMount();
204 + }
205
206 // setState may have been called during cWM
207 if (beforeState !== this._newState) {
@@ -144,16 +214,44 @@ class ReactShallowRenderer {
214 // because DOM refs are not available.
215 }
216
147 - _updateClassComponent(type, props, context) {
217 + _updateClassComponent(element, context) {
218 + const {props, type} = element;
219 +
220 const oldState = this._instance.state || emptyObject;
221 const oldProps = this._instance.props;
222
151 - if (
152 - oldProps !== props &&
153 - typeof this._instance.componentWillReceiveProps === 'function'
154 - ) {
155 - this._instance.componentWillReceiveProps(props, context);
223 + if (oldProps !== props) {
224 + if (typeof this._instance.componentWillReceiveProps === 'function') {
225 + if (__DEV__) {
226 + if (warnAboutDeprecatedLifecycles) {
227 + const componentName = getName(element.type, this._instance);
228 + if (!didWarnAboutLegacyWillReceiveProps[componentName]) {
229 + warning(
230 + false,
231 + '%s: componentWillReceiveProps() is deprecated and ' +
232 + 'will be removed in the next major version. Use ' +
233 + 'static getDerivedStateFromProps() instead. Read about the ' +
234 + 'motivations behind this change: ' +
235 + 'https://fb.me/react-async-component-lifecycle-hooks' +
236 + '\n\n' +
237 + 'As a temporary workaround, you can rename to ' +
238 + 'UNSAFE_componentWillReceiveProps instead.',
239 + componentName,
240 + );
241 + didWarnAboutLegacyWillReceiveProps[componentName] = true;
242 + }
243 + }
244 + }
245 + this._instance.componentWillReceiveProps(props, context);
246 + } else if (
247 + typeof this._instance.UNSAFE_componentWillReceiveProps === 'function'
248 + ) {
249 + this._instance.UNSAFE_componentWillReceiveProps(props, context);
250 + }
251 +
252 + this._updateStateFromStaticLifecycle(props);
253 }
254 +
255 // Read state after cWRP in case it calls setState
256 const state = this._newState || oldState;
257
@@ -174,7 +272,31 @@ class ReactShallowRenderer {
272
273 if (shouldUpdate) {
274 if (typeof this._instance.componentWillUpdate === 'function') {
275 + if (__DEV__) {
276 + if (warnAboutDeprecatedLifecycles) {
277 + const componentName = getName(element.type, this._instance);
278 + if (!didWarnAboutLegacyWillUpdate[componentName]) {
279 + warning(
280 + false,
281 + '%s: componentWillUpdate() is deprecated and will be ' +
282 + 'removed in the next major version. Read about the motivations ' +
283 + 'behind this change: ' +
284 + 'https://fb.me/react-async-component-lifecycle-hooks' +
285 + '\n\n' +
286 + 'As a temporary workaround, you can rename to ' +
287 + 'UNSAFE_componentWillUpdate instead.',
288 + componentName,
289 + );
290 + didWarnAboutLegacyWillUpdate[componentName] = true;
291 + }
292 + }
293 + }
294 +
295 this._instance.componentWillUpdate(props, state, context);
296 + } else if (
297 + typeof this._instance.UNSAFE_componentWillUpdate === 'function'
298 + ) {
299 + this._instance.UNSAFE_componentWillUpdate(props, state, context);
300 }
301 }
302
@@ -188,6 +310,58 @@ class ReactShallowRenderer {
310 // Intentionally do not call componentDidUpdate()
311 // because DOM refs are not available.
312 }
313 +
314 + _updateStateFromStaticLifecycle(props) {
315 + const {type} = this._element;
316 +
317 + if (typeof type.getDerivedStateFromProps === 'function') {
318 + if (__DEV__) {
319 + if (
320 + typeof this._instance.componentWillReceiveProps === 'function' ||
321 + typeof this._instance.UNSAFE_componentWillReceiveProps === 'function'
322 + ) {
323 + const componentName = getName(type, this._instance);
324 + if (!didWarnAboutWillReceivePropsAndDerivedState[componentName]) {
325 + warning(
326 + false,
327 + '%s: Defines both componentWillReceiveProps() and static ' +
328 + 'getDerivedStateFromProps() methods. We recommend using ' +
329 + 'only getDerivedStateFromProps().',
330 + componentName,
331 + );
332 + didWarnAboutWillReceivePropsAndDerivedState[componentName] = true;
333 + }
334 + }
335 + }
336 +
337 + const partialState = type.getDerivedStateFromProps.call(
338 + null,
339 + props,
340 + this._instance.state,
341 + );
342 +
343 + if (__DEV__) {
344 + if (partialState === undefined) {
345 + const componentName = getName(type, this._instance);
346 + if (!didWarnAboutUndefinedDerivedState[componentName]) {
347 + warning(
348 + false,
349 + '%s.getDerivedStateFromProps(): A valid state object (or null) must be returned. ' +
350 + 'You have returned undefined.',
351 + componentName,
352 + );
353 + didWarnAboutUndefinedDerivedState[componentName] = componentName;
354 + }
355 + }
356 + }
357 +
358 + if (partialState != null) {
359 + const oldState = this._newState || this._instance.state;
360 + const newState = Object.assign({}, oldState, partialState);
361 + this._instance.state = this._newState = newState;
362 + }
363 + }
364 + }
365 }
366
367 class Updater {
packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.internal.js new
+53
@@ -0,0 +1,53 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + * @jest-environment node
9 + */
10 +
11 +'use strict';
12 +
13 +let createRenderer;
14 +let React;
15 +let ReactFeatureFlags;
16 +
17 +describe('ReactShallowRenderer', () => {
18 + beforeEach(() => {
19 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
20 + ReactFeatureFlags.warnAboutDeprecatedLifecycles = true;
21 +
22 + createRenderer = require('react-test-renderer/shallow').createRenderer;
23 + React = require('react');
24 + });
25 +
26 + // TODO (RFC #6) Merge this back into ReactShallowRenderer-test once
27 + // the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
28 + it('should warn if deprecated lifecycles exist', () => {
29 + class ComponentWithWarnings extends React.Component {
30 + componentWillReceiveProps() {}
31 + componentWillMount() {}
32 + componentWillUpdate() {}
33 + render() {
34 + return null;
35 + }
36 + }
37 +
38 + const shallowRenderer = createRenderer();
39 + expect(() => shallowRenderer.render(<ComponentWithWarnings />)).toWarnDev(
40 + 'Warning: ComponentWithWarnings: componentWillMount() is deprecated and will ' +
41 + 'be removed in the next major version.',
42 + );
43 + expect(() => shallowRenderer.render(<ComponentWithWarnings />)).toWarnDev([
44 + 'Warning: ComponentWithWarnings: componentWillReceiveProps() is deprecated ' +
45 + 'and will be removed in the next major version.',
46 + 'Warning: ComponentWithWarnings: componentWillUpdate() is deprecated and will ' +
47 + 'be removed in the next major version.',
48 + ]);
49 +
50 + // Verify no duplicate warnings
51 + shallowRenderer.render(<ComponentWithWarnings />);
52 + });
53 +});
packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
+167 -17
@@ -16,6 +16,8 @@ let React;
16
17 describe('ReactShallowRenderer', () => {
18 beforeEach(() => {
19 + jest.resetModules();
20 +
21 createRenderer = require('react-test-renderer/shallow').createRenderer;
22 PropTypes = require('prop-types');
23 React = require('react');
@@ -26,11 +28,13 @@ describe('ReactShallowRenderer', () => {
28 const logger = message => () => logs.push(message) || true;
29
30 class SomeComponent extends React.Component {
29 - componentWillMount = logger('componentWillMount');
31 + state = {};
32 + static getDerivedStateFromProps = logger('getDerivedStateFromProps');
33 + UNSAFE_componentWillMount = logger('componentWillMount');
34 componentDidMount = logger('componentDidMount');
31 - componentWillReceiveProps = logger('componentWillReceiveProps');
35 + UNSAFE_componentWillReceiveProps = logger('componentWillReceiveProps');
36 shouldComponentUpdate = logger('shouldComponentUpdate');
33 - componentWillUpdate = logger('componentWillUpdate');
37 + UNSAFE_componentWillUpdate = logger('componentWillUpdate');
38 componentDidUpdate = logger('componentDidUpdate');
39 componentWillUnmount = logger('componentWillUnmount');
40 render() {
@@ -39,11 +43,16 @@ describe('ReactShallowRenderer', () => {
43 }
44
45 const shallowRenderer = createRenderer();
42 - shallowRenderer.render(<SomeComponent foo={1} />);
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 + );
52
53 // Calling cDU might lead to problems with host component references.
54 // Since our components aren't really mounted, refs won't be available.
46 - expect(logs).toEqual(['componentWillMount']);
55 + expect(logs).toEqual(['getDerivedStateFromProps', 'componentWillMount']);
56
57 logs.splice(0);
58
@@ -59,6 +68,7 @@ describe('ReactShallowRenderer', () => {
68 // The previous shallow renderer did not trigger cDU for props changes.
69 expect(logs).toEqual([
70 'componentWillReceiveProps',
71 + 'getDerivedStateFromProps',
72 'shouldComponentUpdate',
73 'componentWillUpdate',
74 ]);
@@ -416,6 +426,7 @@ describe('ReactShallowRenderer', () => {
426 const componentDidUpdateParams = [];
427 const componentWillReceivePropsParams = [];
428 const componentWillUpdateParams = [];
429 + const getDerivedStateFromPropsParams = [];
430 const setStateParams = [];
431 const shouldComponentUpdateParams = [];
432
@@ -437,14 +448,18 @@ describe('ReactShallowRenderer', () => {
448 componentDidUpdate(...args) {
449 componentDidUpdateParams.push(...args);
450 }
440 - componentWillReceiveProps(...args) {
451 + static getDerivedStateFromProps(...args) {
452 + getDerivedStateFromPropsParams.push(args);
453 + return null;
454 + }
455 + UNSAFE_componentWillReceiveProps(...args) {
456 componentWillReceivePropsParams.push(...args);
457 this.setState((...innerArgs) => {
458 setStateParams.push(...innerArgs);
459 return updatedState;
460 });
461 }
447 - componentWillUpdate(...args) {
462 + UNSAFE_componentWillUpdate(...args) {
463 componentWillUpdateParams.push(...args);
464 }
465 shouldComponentUpdate(...args) {
@@ -458,11 +473,21 @@ describe('ReactShallowRenderer', () => {
473
474 const shallowRenderer = createRenderer();
475
461 - // No lifecycle hooks should be invoked on initial render
462 - shallowRenderer.render(
463 - React.createElement(SimpleComponent, initialProp),
464 - initialContext,
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().',
487 );
488 + expect(getDerivedStateFromPropsParams).toEqual([
489 + [initialProp, initialState],
490 + ]);
491 expect(componentDidUpdateParams).toEqual([]);
492 expect(componentWillReceivePropsParams).toEqual([]);
493 expect(componentWillUpdateParams).toEqual([]);
@@ -479,6 +504,10 @@ describe('ReactShallowRenderer', () => {
504 updatedContext,
505 ]);
506 expect(setStateParams).toEqual([initialState, initialProp]);
507 + expect(getDerivedStateFromPropsParams).toEqual([
508 + [initialProp, initialState],
509 + [updatedProp, initialState],
510 + ]);
511 expect(shouldComponentUpdateParams).toEqual([
512 updatedProp,
513 updatedState,
@@ -523,9 +552,34 @@ describe('ReactShallowRenderer', () => {
552 expect(result.props.className).toEqual('clicked');
553 });
554
555 + it('can initialize state via static getDerivedStateFromProps', () => {
556 + class SimpleComponent extends React.Component {
557 + state = {
558 + count: 1,
559 + };
560 +
561 + static getDerivedStateFromProps(props, prevState) {
562 + return {
563 + count: prevState.count + props.incrementBy,
564 + other: 'foobar',
565 + };
566 + }
567 +
568 + render() {
569 + return (
570 + <div>{`count:${this.state.count}, other:${this.state.other}`}</div>
571 + );
572 + }
573 + }
574 +
575 + const shallowRenderer = createRenderer();
576 + const result = shallowRenderer.render(<SimpleComponent incrementBy={2} />);
577 + expect(result).toEqual(<div>count:3, other:foobar</div>);
578 + });
579 +
580 it('can setState in componentWillMount when shallow rendering', () => {
581 class SimpleComponent extends React.Component {
528 - componentWillMount() {
582 + UNSAFE_componentWillMount() {
583 this.setState({groovy: 'doovy'});
584 }
585
@@ -545,7 +599,7 @@ describe('ReactShallowRenderer', () => {
599 separator: '-',
600 };
601
548 - componentWillMount() {
602 + UNSAFE_componentWillMount() {
603 this.setState({groovy: 'doovy'});
604 this.setState({doovy: 'groovy'});
605 }
@@ -568,7 +622,7 @@ describe('ReactShallowRenderer', () => {
622 separator: '-',
623 };
624
571 - componentWillMount() {
625 + UNSAFE_componentWillMount() {
626 this.setState(state => ({groovy: 'doovy'}));
627 this.setState(state => ({doovy: state.groovy}));
628 }
@@ -589,7 +643,7 @@ describe('ReactShallowRenderer', () => {
643 class SimpleComponent extends React.Component {
644 state = {count: 0};
645
592 - componentWillReceiveProps(nextProps) {
646 + UNSAFE_componentWillReceiveProps(nextProps) {
647 if (nextProps.updateState) {
648 this.setState({count: 1});
649 }
@@ -610,6 +664,40 @@ describe('ReactShallowRenderer', () => {
664 expect(result.props.children).toEqual(1);
665 });
666
667 + it('can update state with static getDerivedStateFromProps when shallow rendering', () => {
668 + class SimpleComponent extends React.Component {
669 + state = {count: 1};
670 +
671 + static getDerivedStateFromProps(nextProps, prevState) {
672 + if (nextProps.updateState) {
673 + return {count: nextProps.incrementBy + prevState.count};
674 + }
675 +
676 + return null;
677 + }
678 +
679 + render() {
680 + return <div>{this.state.count}</div>;
681 + }
682 + }
683 +
684 + const shallowRenderer = createRenderer();
685 + let result = shallowRenderer.render(
686 + <SimpleComponent updateState={false} incrementBy={0} />,
687 + );
688 + expect(result.props.children).toEqual(1);
689 +
690 + result = shallowRenderer.render(
691 + <SimpleComponent updateState={true} incrementBy={2} />,
692 + );
693 + expect(result.props.children).toEqual(3);
694 +
695 + result = shallowRenderer.render(
696 + <SimpleComponent updateState={false} incrementBy={2} />,
697 + );
698 + expect(result.props.children).toEqual(3);
699 + });
700 +
701 it('can setState with an updater function', () => {
702 let instance;
703
@@ -870,7 +958,7 @@ describe('ReactShallowRenderer', () => {
958 };
959 }
960
873 - componentWillMount() {
961 + UNSAFE_componentWillMount() {
962 this.setState(
963 {hasUpdatedState: true},
964 () => (stateSuccessfullyUpdated = this.state.hasUpdatedState),
@@ -899,7 +987,7 @@ describe('ReactShallowRenderer', () => {
987 };
988 }
989
902 - componentWillMount() {
990 + UNSAFE_componentWillMount() {
991 this.setState({foo: 'bar'}, () => mockFn());
992 this.setState({foo: 'foobar'}, () => mockFn());
993 }
@@ -984,4 +1072,66 @@ describe('ReactShallowRenderer', () => {
1072
1073 expect(shallowRenderer.getMountedInstance().state).toBeNull();
1074 });
1075 +
1076 + it('should warn if both componentWillReceiveProps and static getDerivedStateFromProps exist', () => {
1077 + class ComponentWithWarnings extends React.Component {
1078 + state = {};
1079 + static getDerivedStateFromProps(props, prevState) {
1080 + return null;
1081 + }
1082 + UNSAFE_componentWillReceiveProps(nextProps) {}
1083 + render() {
1084 + return null;
1085 + }
1086 + }
1087 +
1088 + const shallowRenderer = createRenderer();
1089 + expect(() => shallowRenderer.render(<ComponentWithWarnings />)).toWarnDev(
1090 + 'ComponentWithWarnings: Defines both componentWillReceiveProps() and static ' +
1091 + 'getDerivedStateFromProps() methods. We recommend using ' +
1092 + 'only getDerivedStateFromProps().',
1093 + );
1094 +
1095 + // Should not log duplicate warning
1096 + shallowRenderer.render(<ComponentWithWarnings />);
1097 + });
1098 +
1099 + it('should warn if getDerivedStateFromProps returns undefined', () => {
1100 + class Component extends React.Component {
1101 + state = {};
1102 + static getDerivedStateFromProps() {}
1103 + render() {
1104 + return null;
1105 + }
1106 + }
1107 +
1108 + const shallowRenderer = createRenderer();
1109 + expect(() => shallowRenderer.render(<Component />)).toWarnDev(
1110 + 'Component.getDerivedStateFromProps(): A valid state object (or null) must ' +
1111 + 'be returned. You have returned undefined.',
1112 + );
1113 +
1114 + // De-duped
1115 + shallowRenderer.render(<Component />);
1116 + });
1117 +
1118 + it('should warn if state not initialized before getDerivedStateFromProps', () => {
1119 + class Component extends React.Component {
1120 + static getDerivedStateFromProps() {
1121 + return null;
1122 + }
1123 + render() {
1124 + return null;
1125 + }
1126 + }
1127 +
1128 + const shallowRenderer = createRenderer();
1129 + expect(() => shallowRenderer.render(<Component />)).toWarnDev(
1130 + 'Component: Did not properly initialize state during construction. ' +
1131 + 'Expected state to be an object, but it was undefined.',
1132 + );
1133 +
1134 + // De-duped
1135 + shallowRenderer.render(<Component />);
1136 + });
1137 });
packages/react/src/__tests__/ReactAsyncClassComponent-test.internal.js
+3 -3
@@ -38,16 +38,16 @@ describe('ReactAsyncClassComponent', () => {
38 componentDidUpdate() {
39 log.push('componentDidUpdate');
40 }
41 - componentWillMount() {
41 + UNSAFE_componentWillMount() {
42 log.push('componentWillMount');
43 }
44 - componentWillReceiveProps() {
44 + UNSAFE_componentWillReceiveProps() {
45 log.push('componentWillReceiveProps');
46 }
47 componentWillUnmount() {
48 log.push('componentWillUnmount');
49 }
50 - componentWillUpdate() {
50 + UNSAFE_componentWillUpdate() {
51 log.push('componentWillUpdate');
52 }
53 shouldComponentUpdate() {
packages/react/src/__tests__/ReactCoffeeScriptClass-test.coffee
+88 -4
@@ -98,6 +98,73 @@ describe 'ReactCoffeeScriptClass', ->
98 test React.createElement(Foo), 'SPAN', 'bar'
99 undefined
100
101 + it 'sets initial state with value returned by static getDerivedStateFromProps', ->
102 + class Foo extends React.Component
103 + constructor: (props) ->
104 + super props
105 + @state = foo: null
106 + render: ->
107 + div
108 + className: "#{@state.foo} #{@state.bar}"
109 + Foo.getDerivedStateFromProps = (nextProps, prevState) ->
110 + {
111 + foo: nextProps.foo
112 + bar: 'bar'
113 + }
114 + test React.createElement(Foo, foo: 'foo'), 'DIV', 'foo bar'
115 + undefined
116 +
117 + it 'warns if state not initialized before static getDerivedStateFromProps', ->
118 + class Foo extends React.Component
119 + render: ->
120 + div
121 + className: "#{@state.foo} #{@state.bar}"
122 + Foo.getDerivedStateFromProps = (nextProps, prevState) ->
123 + {
124 + foo: nextProps.foo
125 + bar: 'bar'
126 + }
127 + expect(->
128 + ReactDOM.render(React.createElement(Foo, foo: 'foo'), container)
129 + ).toWarnDev 'Foo: Did not properly initialize state during construction. Expected state to be an object, but it was undefined.'
130 + undefined
131 +
132 + it 'updates initial state with values returned by static getDerivedStateFromProps', ->
133 + class Foo extends React.Component
134 + constructor: (props, context) ->
135 + super props, context
136 + @state =
137 + foo: 'foo'
138 + bar: 'bar'
139 + render: ->
140 + div
141 + className: "#{@state.foo} #{@state.bar}"
142 + Foo.getDerivedStateFromProps = (nextProps, prevState) ->
143 + {
144 + foo: "not-#{prevState.foo}"
145 + }
146 + test React.createElement(Foo), 'DIV', 'not-foo bar'
147 + undefined
148 +
149 + it 'renders updated state with values returned by static getDerivedStateFromProps', ->
150 + class Foo extends React.Component
151 + constructor: (props, context) ->
152 + super props, context
153 + @state =
154 + value: 'initial'
155 + render: ->
156 + div
157 + className: @state.value
158 + Foo.getDerivedStateFromProps = (nextProps, prevState) ->
159 + if nextProps.update
160 + return {
161 + value: 'updated'
162 + }
163 + return null
164 + test React.createElement(Foo, update: false), 'DIV', 'initial'
165 + test React.createElement(Foo, update: true), 'DIV', 'updated'
166 + undefined
167 +
168 it 'renders based on context in the constructor', ->
169 class Foo extends React.Component
170 @contextTypes:
@@ -136,7 +203,7 @@ describe 'ReactCoffeeScriptClass', ->
203 constructor: (props) ->
204 @state = bar: props.initialValue
205
139 - componentWillMount: ->
206 + UNSAFE_componentWillMount: ->
207 @setState bar: 'bar'
208
209 render: ->
@@ -232,20 +299,20 @@ describe 'ReactCoffeeScriptClass', ->
299 constructor: ->
300 @state = {}
301
235 - componentWillMount: ->
302 + UNSAFE_componentWillMount: ->
303 lifeCycles.push 'will-mount'
304
305 componentDidMount: ->
306 lifeCycles.push 'did-mount'
307
241 - componentWillReceiveProps: (nextProps) ->
308 + UNSAFE_componentWillReceiveProps: (nextProps) ->
309 lifeCycles.push 'receive-props', nextProps
310
311 shouldComponentUpdate: (nextProps, nextState) ->
312 lifeCycles.push 'should-update', nextProps, nextState
313 true
314
248 - componentWillUpdate: (nextProps, nextState) ->
315 + UNSAFE_componentWillUpdate: (nextProps, nextState) ->
316 lifeCycles.push 'will-update', nextProps, nextState
317
318 componentDidUpdate: (prevProps, prevState) ->
@@ -361,6 +428,23 @@ describe 'ReactCoffeeScriptClass', ->
428 )
429 undefined
430
431 + it 'should warn when misspelling UNSAFE_componentWillReceiveProps', ->
432 + class NamedComponent extends React.Component
433 + UNSAFE_componentWillRecieveProps: ->
434 + false
435 +
436 + render: ->
437 + span
438 + className: 'foo'
439 +
440 + expect(->
441 + test React.createElement(NamedComponent), 'SPAN', 'foo'
442 + ).toWarnDev(
443 + 'Warning: NamedComponent has a method called UNSAFE_componentWillRecieveProps().
444 + Did you mean UNSAFE_componentWillReceiveProps()?'
445 + )
446 + undefined
447 +
448 it 'should throw AND warn when trying to access classic APIs', ->
449 instance =
450 test Inner(name: 'foo'), 'DIV', 'foo'
packages/react/src/__tests__/ReactContextValidator-test.js
+2 -2
@@ -89,7 +89,7 @@ describe('ReactContextValidator', () => {
89 };
90
91 class Component extends React.Component {
92 - componentWillReceiveProps(nextProps, nextContext) {
92 + UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
93 actualComponentWillReceiveProps = nextContext;
94 return true;
95 }
@@ -99,7 +99,7 @@ describe('ReactContextValidator', () => {
99 return true;
100 }
101
102 - componentWillUpdate(nextProps, nextState, nextContext) {
102 + UNSAFE_componentWillUpdate(nextProps, nextState, nextContext) {
103 actualComponentWillUpdate = nextContext;
104 }
105
packages/react/src/__tests__/ReactES6Class-test.js
+94 -4
@@ -109,6 +109,79 @@ describe('ReactES6Class', () => {
109 test(<Foo />, 'SPAN', 'bar');
110 });
111
112 + it('sets initial state with value returned by static getDerivedStateFromProps', () => {
113 + class Foo extends React.Component {
114 + state = {};
115 + static getDerivedStateFromProps(nextProps, prevState) {
116 + return {
117 + foo: nextProps.foo,
118 + bar: 'bar',
119 + };
120 + }
121 + render() {
122 + return <div className={`${this.state.foo} ${this.state.bar}`} />;
123 + }
124 + }
125 + test(<Foo foo="foo" />, 'DIV', 'foo bar');
126 + });
127 +
128 + it('warns if state not initialized before static getDerivedStateFromProps', () => {
129 + class Foo extends React.Component {
130 + static getDerivedStateFromProps(nextProps, prevState) {
131 + return {
132 + foo: nextProps.foo,
133 + bar: 'bar',
134 + };
135 + }
136 + render() {
137 + return <div className={`${this.state.foo} ${this.state.bar}`} />;
138 + }
139 + }
140 + expect(() => ReactDOM.render(<Foo foo="foo" />, container)).toWarnDev(
141 + 'Foo: Did not properly initialize state during construction. ' +
142 + 'Expected state to be an object, but it was undefined.',
143 + );
144 + });
145 +
146 + it('updates initial state with values returned by static getDerivedStateFromProps', () => {
147 + class Foo extends React.Component {
148 + state = {
149 + foo: 'foo',
150 + bar: 'bar',
151 + };
152 + static getDerivedStateFromProps(nextProps, prevState) {
153 + return {
154 + foo: `not-${prevState.foo}`,
155 + };
156 + }
157 + render() {
158 + return <div className={`${this.state.foo} ${this.state.bar}`} />;
159 + }
160 + }
161 + test(<Foo />, 'DIV', 'not-foo bar');
162 + });
163 +
164 + it('renders updated state with values returned by static getDerivedStateFromProps', () => {
165 + class Foo extends React.Component {
166 + state = {
167 + value: 'initial',
168 + };
169 + static getDerivedStateFromProps(nextProps, prevState) {
170 + if (nextProps.update) {
171 + return {
172 + value: 'updated',
173 + };
174 + }
175 + return null;
176 + }
177 + render() {
178 + return <div className={this.state.value} />;
179 + }
180 + }
181 + test(<Foo update={false} />, 'DIV', 'initial');
182 + test(<Foo update={true} />, 'DIV', 'updated');
183 + });
184 +
185 it('renders based on context in the constructor', () => {
186 class Foo extends React.Component {
187 constructor(props, context) {
@@ -147,7 +220,7 @@ describe('ReactES6Class', () => {
220 super(props);
221 this.state = {bar: props.initialValue};
222 }
150 - componentWillMount() {
223 + UNSAFE_componentWillMount() {
224 this.setState({bar: 'bar'});
225 }
226 render() {
@@ -257,20 +330,20 @@ describe('ReactES6Class', () => {
330 super();
331 this.state = {};
332 }
260 - componentWillMount() {
333 + UNSAFE_componentWillMount() {
334 lifeCycles.push('will-mount');
335 }
336 componentDidMount() {
337 lifeCycles.push('did-mount');
338 }
266 - componentWillReceiveProps(nextProps) {
339 + UNSAFE_componentWillReceiveProps(nextProps) {
340 lifeCycles.push('receive-props', nextProps);
341 }
342 shouldComponentUpdate(nextProps, nextState) {
343 lifeCycles.push('should-update', nextProps, nextState);
344 return true;
345 }
273 - componentWillUpdate(nextProps, nextState) {
346 + UNSAFE_componentWillUpdate(nextProps, nextState) {
347 lifeCycles.push('will-update', nextProps, nextState);
348 }
349 componentDidUpdate(prevProps, prevState) {
@@ -379,6 +452,23 @@ describe('ReactES6Class', () => {
452 );
453 });
454
455 + it('should warn when misspelling UNSAFE_componentWillReceiveProps', () => {
456 + class NamedComponent extends React.Component {
457 + UNSAFE_componentWillRecieveProps() {
458 + return false;
459 + }
460 + render() {
461 + return <span className="foo" />;
462 + }
463 + }
464 +
465 + expect(() => test(<NamedComponent />, 'SPAN', 'foo')).toWarnDev(
466 + 'Warning: ' +
467 + 'NamedComponent has a method called UNSAFE_componentWillRecieveProps(). ' +
468 + 'Did you mean UNSAFE_componentWillReceiveProps()?',
469 + );
470 + });
471 +
472 it('should throw AND warn when trying to access classic APIs', () => {
473 const instance = test(<Inner name="foo" />, 'DIV', 'foo');
474 expect(() =>
packages/react/src/__tests__/ReactTypeScriptClass-test.ts
+102 -4
@@ -117,7 +117,7 @@ class RenderOnce extends React.Component {
117 state = {
118 bar: this.props.initialValue,
119 };
120 - componentWillMount() {
120 + UNSAFE_componentWillMount() {
121 this.setState({bar: 'bar'});
122 }
123 render() {
@@ -206,20 +206,20 @@ let lifeCycles = [];
206 class NormalLifeCycles extends React.Component {
207 props: any;
208 state = {};
209 - componentWillMount() {
209 + UNSAFE_componentWillMount() {
210 lifeCycles.push('will-mount');
211 }
212 componentDidMount() {
213 lifeCycles.push('did-mount');
214 }
215 - componentWillReceiveProps(nextProps) {
215 + UNSAFE_componentWillReceiveProps(nextProps) {
216 lifeCycles.push('receive-props', nextProps);
217 }
218 shouldComponentUpdate(nextProps, nextState) {
219 lifeCycles.push('should-update', nextProps, nextState);
220 return true;
221 }
222 - componentWillUpdate(nextProps, nextState) {
222 + UNSAFE_componentWillUpdate(nextProps, nextState) {
223 lifeCycles.push('will-update', nextProps, nextState);
224 }
225 componentDidUpdate(prevProps, prevState) {
@@ -273,6 +273,16 @@ class MisspelledComponent2 extends React.Component {
273 }
274 }
275
276 +// it should warn when misspelling UNSAFE_componentWillReceiveProps
277 +class MisspelledComponent3 extends React.Component {
278 + UNSAFE_componentWillRecieveProps() {
279 + return false;
280 + }
281 + render() {
282 + return React.createElement('span', {className: 'foo'});
283 + }
284 +}
285 +
286 // it supports this.context passed via getChildContext
287 class ReadContext extends React.Component {
288 static contextTypes = {bar: PropTypes.string};
@@ -344,6 +354,84 @@ describe('ReactTypeScriptClass', function() {
354 test(React.createElement(StateBasedOnProps), 'SPAN', 'bar');
355 });
356
357 + it('sets initial state with value returned by static getDerivedStateFromProps', function() {
358 + class Foo extends React.Component {
359 + state = {
360 + foo: null,
361 + bar: null,
362 + };
363 + static getDerivedStateFromProps(nextProps, prevState) {
364 + return {
365 + foo: nextProps.foo,
366 + bar: 'bar',
367 + };
368 + }
369 + render() {
370 + return React.createElement('div', {className: `${this.state.foo} ${this.state.bar}`});
371 + }
372 + }
373 + test(React.createElement(Foo, {foo: "foo"}), 'DIV', 'foo bar');
374 + });
375 +
376 + it('warns if state not initialized before static getDerivedStateFromProps', function() {
377 + class Foo extends React.Component {
378 + static getDerivedStateFromProps(nextProps, prevState) {
379 + return {
380 + foo: nextProps.foo,
381 + bar: 'bar',
382 + };
383 + }
384 + render() {
385 + return React.createElement('div', {className: `${this.state.foo} ${this.state.bar}`});
386 + }
387 + }
388 + expect(function() {
389 + ReactDOM.render(React.createElement(Foo, {foo: "foo"}), container);
390 + }).toWarnDev(
391 + 'Foo: Did not properly initialize state during construction. ' +
392 + 'Expected state to be an object, but it was undefined.'
393 + );
394 + });
395 +
396 + it('updates initial state with values returned by static getDerivedStateFromProps', function() {
397 + class Foo extends React.Component {
398 + state = {
399 + foo: 'foo',
400 + bar: 'bar',
401 + };
402 + static getDerivedStateFromProps(nextProps, prevState) {
403 + return {
404 + foo: `not-${prevState.foo}`,
405 + };
406 + }
407 + render() {
408 + return React.createElement('div', {className: `${this.state.foo} ${this.state.bar}`});
409 + }
410 + }
411 + test(React.createElement(Foo), 'DIV', 'not-foo bar');
412 + });
413 +
414 + it('renders updated state with values returned by static getDerivedStateFromProps', function() {
415 + class Foo extends React.Component {
416 + state = {
417 + value: 'initial',
418 + };
419 + static getDerivedStateFromProps(nextProps, prevState) {
420 + if (nextProps.update) {
421 + return {
422 + value: 'updated',
423 + };
424 + }
425 + return null;
426 + }
427 + render() {
428 + return React.createElement('div', {className: this.state.value});
429 + }
430 + }
431 + test(React.createElement(Foo, {update:false}), 'DIV', 'initial');
432 + test(React.createElement(Foo, {update:true}), 'DIV', 'updated');
433 + });
434 +
435 it('renders based on context in the constructor', function() {
436 test(React.createElement(ProvideChildContextTypes), 'SPAN', 'foo');
437 });
@@ -483,6 +571,16 @@ describe('ReactTypeScriptClass', function() {
571 );
572 });
573
574 + it('should warn when misspelling UNSAFE_componentWillReceiveProps', function() {
575 + expect(() =>
576 + test(React.createElement(MisspelledComponent3), 'SPAN', 'foo')
577 + ).toWarnDev(
578 + 'Warning: ' +
579 + 'MisspelledComponent3 has a method called UNSAFE_componentWillRecieveProps(). ' +
580 + 'Did you mean UNSAFE_componentWillReceiveProps()?'
581 + );
582 + });
583 +
584 it('should throw AND warn when trying to access classic APIs', function() {
585 const instance = test(
586 React.createElement(Inner, {name: 'foo'}),
packages/react/src/__tests__/createReactClassIntegration-test.internal.js new
+126
@@ -0,0 +1,126 @@
1 +/**
2 + * Copyright (c) 2013-present, Facebook, Inc.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +let React;
13 +let ReactDOM;
14 +let ReactFeatureFlags;
15 +let createReactClass;
16 +
17 +describe('create-react-class-integration', () => {
18 + beforeEach(() => {
19 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
20 + ReactFeatureFlags.warnAboutDeprecatedLifecycles = true;
21 +
22 + React = require('react');
23 + ReactDOM = require('react-dom');
24 + createReactClass = require('create-react-class/factory')(
25 + React.Component,
26 + React.isValidElement,
27 + new React.Component().updater,
28 + );
29 + });
30 +
31 + // TODO (RFC #6) Merge this back into createReactClassIntegration-test once
32 + // the 'warnAboutDeprecatedLifecycles' feature flag has been removed.
33 + it('isMounted works', () => {
34 + const ops = [];
35 + let instance;
36 + const Component = createReactClass({
37 + displayName: 'MyComponent',
38 + mixins: [
39 + {
40 + componentWillMount() {
41 + this.log('mixin.componentWillMount');
42 + },
43 + componentDidMount() {
44 + this.log('mixin.componentDidMount');
45 + },
46 + componentWillUpdate() {
47 + this.log('mixin.componentWillUpdate');
48 + },
49 + componentDidUpdate() {
50 + this.log('mixin.componentDidUpdate');
51 + },
52 + componentWillUnmount() {
53 + this.log('mixin.componentWillUnmount');
54 + },
55 + },
56 + ],
57 + log(name) {
58 + ops.push(`${name}: ${this.isMounted()}`);
59 + },
60 + getInitialState() {
61 + this.log('getInitialState');
62 + return {};
63 + },
64 + componentWillMount() {
65 + this.log('componentWillMount');
66 + },
67 + componentDidMount() {
68 + this.log('componentDidMount');
69 + },
70 + componentWillUpdate() {
71 + this.log('componentWillUpdate');
72 + },
73 + componentDidUpdate() {
74 + this.log('componentDidUpdate');
75 + },
76 + componentWillUnmount() {
77 + this.log('componentWillUnmount');
78 + },
79 + render() {
80 + instance = this;
81 + this.log('render');
82 + return <div />;
83 + },
84 + });
85 +
86 + const container = document.createElement('div');
87 +
88 + // TODO (RFC #6) The below lifecycle warnings are unavoidable for now,
89 + // Until create-react-class recognizes the UNSAFE_* methods.
90 + // (If we try to use them before them, it will error because
91 + // we are defining the same method twice.)
92 + // Update the above component to use the new UNSAFE_* methods
93 + // (and remove the expected warnings) once create-react-class has been updated.
94 + expect(() => ReactDOM.render(<Component />, container)).toWarnDev([
95 + 'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
96 + 'clean up subscriptions and pending requests in componentWillUnmount ' +
97 + 'to prevent memory leaks.',
98 + 'Warning: MyComponent: componentWillMount() is deprecated and will be ' +
99 + 'removed in the next major version.',
100 + ]);
101 +
102 + expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
103 + 'Warning: MyComponent: componentWillUpdate() is deprecated and will be ' +
104 + 'removed in the next major version.',
105 + );
106 +
107 + ReactDOM.unmountComponentAtNode(container);
108 + instance.log('after unmount');
109 + expect(ops).toEqual([
110 + 'getInitialState: false',
111 + 'mixin.componentWillMount: false',
112 + 'componentWillMount: false',
113 + 'render: false',
114 + 'mixin.componentDidMount: true',
115 + 'componentDidMount: true',
116 + 'mixin.componentWillUpdate: true',
117 + 'componentWillUpdate: true',
118 + 'render: true',
119 + 'mixin.componentDidUpdate: true',
120 + 'componentDidUpdate: true',
121 + 'mixin.componentWillUnmount: true',
122 + 'componentWillUnmount: true',
123 + 'after unmount: false',
124 + ]);
125 + });
126 +});
packages/react/src/__tests__/createReactClassIntegration-test.js
+92 -70
@@ -17,6 +17,7 @@ let createReactClass;
17
18 describe('create-react-class-integration', () => {
19 beforeEach(() => {
20 + jest.resetModules();
21 PropTypes = require('prop-types');
22 React = require('react');
23 ReactDOM = require('react-dom');
@@ -151,6 +152,23 @@ describe('create-react-class-integration', () => {
152 );
153 });
154
155 + // TODO (RFC #6) Reenable after create-react-class updated.
156 + xit('should warn when misspelling UNSAFE_componentWillReceiveProps', () => {
157 + expect(() =>
158 + createReactClass({
159 + UNSAFE_componentWillRecieveProps: function() {
160 + return false;
161 + },
162 + render: function() {
163 + return <div />;
164 + },
165 + }),
166 + ).toWarnDev(
167 + 'Warning: A component has a method called UNSAFE_componentWillRecieveProps(). ' +
168 + 'Did you mean UNSAFE_componentWillReceiveProps()?',
169 + );
170 + });
171 +
172 it('should throw if a reserved property is in statics', () => {
173 expect(function() {
174 createReactClass({
@@ -175,7 +193,6 @@ describe('create-react-class-integration', () => {
193 });
194
195 // TODO: Consider actually moving these to statics or drop this unit test.
178 -
196 xit('should warn when using deprecated non-static spec keys', () => {
197 expect(() =>
198 createReactClass({
@@ -251,6 +268,23 @@ describe('create-react-class-integration', () => {
268 expect(instance.state.occupation).toEqual('clown');
269 });
270
271 + it('should work with getDerivedStateFromProps() return values', () => {
272 + const Component = createReactClass({
273 + getInitialState() {
274 + return {};
275 + },
276 + render: function() {
277 + return <span />;
278 + },
279 + });
280 + Component.getDerivedStateFromProps = () => {
281 + return {occupation: 'clown'};
282 + };
283 + let instance = <Component />;
284 + instance = ReactTestUtils.renderIntoDocument(instance);
285 + expect(instance.state.occupation).toEqual('clown');
286 + });
287 +
288 it('renders based on context getInitialState', () => {
289 const Foo = createReactClass({
290 contextTypes: {
@@ -346,85 +380,73 @@ describe('create-react-class-integration', () => {
380 expect(ops).toEqual(['Render: 0', 'Render: 1', 'Callback: 1']);
381 });
382
349 - it('isMounted works', () => {
350 - const ops = [];
383 + it('getDerivedStateFromProps updates state when props change', () => {
384 + const Component = createReactClass({
385 + getInitialState() {
386 + return {
387 + count: 1,
388 + };
389 + },
390 + render() {
391 + return <div>count:{this.state.count}</div>;
392 + },
393 + });
394 + Component.getDerivedStateFromProps = (nextProps, prevState) => ({
395 + count: prevState.count + nextProps.incrementBy,
396 + });
397 +
398 + const container = document.createElement('div');
399 + const instance = ReactDOM.render(
400 + <div>
401 + <Component incrementBy={0} />
402 + </div>,
403 + container,
404 + );
405 + expect(instance.textContent).toEqual('count:1');
406 + ReactDOM.render(
407 + <div>
408 + <Component incrementBy={2} />
409 + </div>,
410 + container,
411 + );
412 + expect(instance.textContent).toEqual('count:3');
413 + });
414 +
415 + it('should support the new static getDerivedStateFromProps method', () => {
416 let instance;
417 const Component = createReactClass({
353 - displayName: 'MyComponent',
354 - mixins: [
355 - {
356 - componentWillMount() {
357 - this.log('mixin.componentWillMount');
358 - },
359 - componentDidMount() {
360 - this.log('mixin.componentDidMount');
361 - },
362 - componentWillUpdate() {
363 - this.log('mixin.componentWillUpdate');
364 - },
365 - componentDidUpdate() {
366 - this.log('mixin.componentDidUpdate');
367 - },
368 - componentWillUnmount() {
369 - this.log('mixin.componentWillUnmount');
370 - },
418 + statics: {
419 + getDerivedStateFromProps: function() {
420 + return {foo: 'bar'};
421 },
372 - ],
373 - log(name) {
374 - ops.push(`${name}: ${this.isMounted()}`);
422 },
423 +
424 getInitialState() {
377 - this.log('getInitialState');
425 return {};
426 },
380 - componentWillMount() {
381 - this.log('componentWillMount');
382 - },
383 - componentDidMount() {
384 - this.log('componentDidMount');
385 - },
386 - componentWillUpdate() {
387 - this.log('componentWillUpdate');
388 - },
389 - componentDidUpdate() {
390 - this.log('componentDidUpdate');
391 - },
392 - componentWillUnmount() {
393 - this.log('componentWillUnmount');
394 - },
395 - render() {
427 +
428 + render: function() {
429 instance = this;
397 - this.log('render');
398 - return <div />;
430 + return null;
431 },
432 });
433 + ReactDOM.render(<Component />, document.createElement('div'));
434 + expect(instance.state.foo).toBe('bar');
435 + });
436
402 - const container = document.createElement('div');
403 -
404 - expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
405 - 'Warning: MyComponent: isMounted is deprecated. Instead, make sure to ' +
406 - 'clean up subscriptions and pending requests in componentWillUnmount ' +
407 - 'to prevent memory leaks.',
408 - );
409 -
410 - ReactDOM.render(<Component />, container);
411 - ReactDOM.unmountComponentAtNode(container);
412 - instance.log('after unmount');
413 - expect(ops).toEqual([
414 - 'getInitialState: false',
415 - 'mixin.componentWillMount: false',
416 - 'componentWillMount: false',
417 - 'render: false',
418 - 'mixin.componentDidMount: true',
419 - 'componentDidMount: true',
420 - 'mixin.componentWillUpdate: true',
421 - 'componentWillUpdate: true',
422 - 'render: true',
423 - 'mixin.componentDidUpdate: true',
424 - 'componentDidUpdate: true',
425 - 'mixin.componentWillUnmount: true',
426 - 'componentWillUnmount: true',
427 - 'after unmount: false',
428 - ]);
437 + it('should warn if state is not properly initialized before getDerivedStateFromProps', () => {
438 + const Component = createReactClass({
439 + statics: {
440 + getDerivedStateFromProps: function() {
441 + return null;
442 + },
443 + },
444 + render: function() {
445 + return null;
446 + },
447 + });
448 + expect(() =>
449 + ReactDOM.render(<Component />, document.createElement('div')),
450 + ).toWarnDev('Did not properly initialize state during construction.');
451 });
452 });
packages/shared/ReactFeatureFlags.js
+3
@@ -24,6 +24,9 @@ export const enablePersistentReconciler = false;
24 // Helps identify side effects in begin-phase lifecycle hooks and setState reducers:
25 export const debugRenderPhaseSideEffects = false;
26
27 +// Warn about deprecated, async-unsafe lifecycles; relates to RFC #6:
28 +export const warnAboutDeprecatedLifecycles = false;
29 +
30 // Only used in www builds.
31 export function addUserTimingListener() {
32 invariant(false, 'Not implemented.');
packages/shared/forks/ReactFeatureFlags.native-cs.js
+1
@@ -16,6 +16,7 @@ export const debugRenderPhaseSideEffects = false;
16 export const enableAsyncSubtreeAPI = true;
17 export const enableCreateRoot = false;
18 export const enableUserTimingAPI = __DEV__;
19 +export const warnAboutDeprecatedLifecycles = false;
20
21 // React Native CS uses persistent reconciler.
22 export const enableMutatingReconciler = false;
packages/shared/forks/ReactFeatureFlags.native.js
+4 -1
@@ -13,7 +13,10 @@ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
13 import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.native';
14
15 // Re-export dynamic flags from the fbsource version.
16 -export const {debugRenderPhaseSideEffects} = require('ReactFeatureFlags');
16 +export const {
17 + debugRenderPhaseSideEffects,
18 + warnAboutDeprecatedLifecycles,
19 +} = require('ReactFeatureFlags');
20
21 // The rest of the flags are static for better dead code elimination.
22 export const enableAsyncSubtreeAPI = true;
packages/shared/forks/ReactFeatureFlags.www.js
+4 -1
@@ -11,7 +11,10 @@ import typeof * as FeatureFlagsType from 'shared/ReactFeatureFlags';
11 import typeof * as FeatureFlagsShimType from './ReactFeatureFlags.www';
12
13 // Re-export dynamic flags from the www version.
14 -export const {debugRenderPhaseSideEffects} = require('ReactFeatureFlags');
14 +export const {
15 + debugRenderPhaseSideEffects,
16 + warnAboutDeprecatedLifecycles,
17 +} = require('ReactFeatureFlags');
18
19 // The rest of the flags are static for better dead code elimination.
20 export const enableAsyncSubtreeAPI = true;