Fix shallow renderer set instance state after gDSFP before calling sCU (#14613)
* Fix shallow renderer set instance state after gDSFP before calling sCU * Update ReactShallowRenderer.js * Unwind abstraction * Fewer names
Yi-Shan, Chen committed
Jan 18, 2019 at 10:31 UTC
4f332885a2541e68c573d19ca94c875fdf988a38
3 files changed
+96
-24
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+34
@@ -1201,6 +1201,40 @@ describe('ReactComponentLifeCycle', () => {
1201
expect(log).toEqual([]);
1202
});
1203
1204
+ it('should pass previous state to shouldComponentUpdate even with getDerivedStateFromProps', () => {
1205
+ const divRef = React.createRef();
1206
+ class SimpleComponent extends React.Component {
1207
+ constructor(props) {
1208
+ super(props);
1209
+ this.state = {
1210
+ value: props.value,
1211
+ };
1212
+ }
1213
+
1214
+ static getDerivedStateFromProps(nextProps, prevState) {
1215
+ if (nextProps.value === prevState.value) {
1216
+ return null;
1217
+ }
1218
+ return {value: nextProps.value};
1219
+ }
1220
+
1221
+ shouldComponentUpdate(nextProps, nextState) {
1222
+ return nextState.value !== this.state.value;
1223
+ }
1224
+
1225
+ render() {
1226
+ return <div ref={divRef}>value: {this.state.value}</div>;
1227
+ }
1228
+ }
1229
+
1230
+ const div = document.createElement('div');
1231
+
1232
+ ReactDOM.render(<SimpleComponent value="initial" />, div);
1233
+ expect(divRef.current.textContent).toBe('value: initial');
1234
+ ReactDOM.render(<SimpleComponent value="updated" />, div);
1235
+ expect(divRef.current.textContent).toBe('value: updated');
1236
+ });
1237
+
1238
it('should call getSnapshotBeforeUpdate before mutations are committed', () => {
1239
const log = [];
1240
packages/react-test-renderer/src/ReactShallowRenderer.js
+26
-24
@@ -529,7 +529,20 @@ class ReactShallowRenderer {
529
this._updater,
530
);
531
532
- this._updateStateFromStaticLifecycle(element.props);
532
+ if (typeof element.type.getDerivedStateFromProps === 'function') {
533
+ const partialState = element.type.getDerivedStateFromProps.call(
534
+ null,
535
+ element.props,
536
+ this._instance.state,
537
+ );
538
+ if (partialState != null) {
539
+ this._instance.state = Object.assign(
540
+ {},
541
+ this._instance.state,
542
+ partialState,
543
+ );
544
+ }
545
+ }
546
547
if (element.type.hasOwnProperty('contextTypes')) {
548
currentlyValidatingElement = element;
@@ -653,10 +666,19 @@ class ReactShallowRenderer {
666
}
667
}
668
}
656
- this._updateStateFromStaticLifecycle(props);
669
670
// Read state after cWRP in case it calls setState
659
- const state = this._newState || oldState;
671
+ let state = this._newState || oldState;
672
+ if (typeof type.getDerivedStateFromProps === 'function') {
673
+ const partialState = type.getDerivedStateFromProps.call(
674
+ null,
675
+ props,
676
+ state,
677
+ );
678
+ if (partialState != null) {
679
+ state = Object.assign({}, state, partialState);
680
+ }
681
+ }
682
683
let shouldUpdate = true;
684
if (this._forcedUpdate) {
@@ -692,6 +714,7 @@ class ReactShallowRenderer {
714
this._instance.context = context;
715
this._instance.props = props;
716
this._instance.state = state;
717
+ this._newState = null;
718
719
if (shouldUpdate) {
720
this._rendered = this._instance.render();
@@ -699,27 +722,6 @@ class ReactShallowRenderer {
722
// Intentionally do not call componentDidUpdate()
723
// because DOM refs are not available.
724
}
702
-
703
- _updateStateFromStaticLifecycle(props: Object) {
704
- if (this._element === null) {
705
- return;
706
- }
707
- const {type} = this._element;
708
-
709
- if (typeof type.getDerivedStateFromProps === 'function') {
710
- const oldState = this._newState || this._instance.state;
711
- const partialState = type.getDerivedStateFromProps.call(
712
- null,
713
- props,
714
- oldState,
715
- );
716
-
717
- if (partialState != null) {
718
- const newState = Object.assign({}, oldState, partialState);
719
- this._instance.state = this._newState = newState;
720
- }
721
- }
722
- }
725
}
726
727
let currentlyValidatingElement = null;
packages/react-test-renderer/src/__tests__/ReactShallowRenderer-test.js
+36
@@ -942,6 +942,42 @@ describe('ReactShallowRenderer', () => {
942
expect(result).toEqual(<div>value:1</div>);
943
});
944
945
+ it('should pass previous state to shouldComponentUpdate even with getDerivedStateFromProps', () => {
946
+ class SimpleComponent extends React.Component {
947
+ constructor(props) {
948
+ super(props);
949
+ this.state = {
950
+ value: props.value,
951
+ };
952
+ }
953
+
954
+ static getDerivedStateFromProps(nextProps, prevState) {
955
+ if (nextProps.value === prevState.value) {
956
+ return null;
957
+ }
958
+ return {value: nextProps.value};
959
+ }
960
+
961
+ shouldComponentUpdate(nextProps, nextState) {
962
+ return nextState.value !== this.state.value;
963
+ }
964
+
965
+ render() {
966
+ return <div>{`value:${this.state.value}`}</div>;
967
+ }
968
+ }
969
+
970
+ const shallowRenderer = createRenderer();
971
+ const initialResult = shallowRenderer.render(
972
+ <SimpleComponent value="initial" />,
973
+ );
974
+ expect(initialResult).toEqual(<div>value:initial</div>);
975
+ const updatedResult = shallowRenderer.render(
976
+ <SimpleComponent value="updated" />,
977
+ );
978
+ expect(updatedResult).toEqual(<div>value:updated</div>);
979
+ });
980
+
981
it('can setState with an updater function', () => {
982
let instance;
983