Always keep disabled logs in the second pass (#21739)
* Add tests for disabled logs * Always keep disabled logs in the second pass * Jest nit * Always use the second result
Dan Abramov committed
Jun 24, 2021 at 19:50 UTC
51b0becf3e8a8084d8a2ee85ce8b3a1d9ace6ecd
3 files changed
+220
-36
packages/react-reconciler/src/ReactFiberClassComponent.new.js
+14
-18
@@ -169,7 +169,7 @@ function applyDerivedStateFromProps(
169
nextProps: any,
170
) {
171
const prevState = workInProgress.memoizedState;
172
-
172
+ let partialState = getDerivedStateFromProps(nextProps, prevState);
173
if (__DEV__) {
174
if (
175
debugRenderPhaseSideEffectsForStrictMode &&
@@ -178,16 +178,11 @@ function applyDerivedStateFromProps(
178
disableLogs();
179
try {
180
// Invoke the function an extra time to help detect side-effects.
181
- getDerivedStateFromProps(nextProps, prevState);
181
+ partialState = getDerivedStateFromProps(nextProps, prevState);
182
} finally {
183
reenableLogs();
184
}
185
}
186
- }
187
-
188
- const partialState = getDerivedStateFromProps(nextProps, prevState);
189
-
190
- if (__DEV__) {
186
warnOnUndefinedDerivedState(ctor, partialState);
187
}
188
// Merge the partial state and the previous state.
@@ -323,6 +318,11 @@ function checkShouldComponentUpdate(
318
) {
319
const instance = workInProgress.stateNode;
320
if (typeof instance.shouldComponentUpdate === 'function') {
321
+ let shouldUpdate = instance.shouldComponentUpdate(
322
+ newProps,
323
+ newState,
324
+ nextContext,
325
+ );
326
if (__DEV__) {
327
if (
328
debugRenderPhaseSideEffectsForStrictMode &&
@@ -331,19 +331,15 @@ function checkShouldComponentUpdate(
331
disableLogs();
332
try {
333
// Invoke the function an extra time to help detect side-effects.
334
- instance.shouldComponentUpdate(newProps, newState, nextContext);
334
+ shouldUpdate = instance.shouldComponentUpdate(
335
+ newProps,
336
+ newState,
337
+ nextContext,
338
+ );
339
} finally {
340
reenableLogs();
341
}
342
}
339
- }
340
- const shouldUpdate = instance.shouldComponentUpdate(
341
- newProps,
342
- newState,
343
- nextContext,
344
- );
345
-
346
- if (__DEV__) {
343
if (shouldUpdate === undefined) {
344
console.error(
345
'%s.shouldComponentUpdate(): Returned undefined instead of a ' +
@@ -659,6 +655,7 @@ function constructClassInstance(
655
: emptyContextObject;
656
}
657
658
+ let instance = new ctor(props, context);
659
// Instantiate twice to help detect side-effects.
660
if (__DEV__) {
661
if (
@@ -667,14 +664,13 @@ function constructClassInstance(
664
) {
665
disableLogs();
666
try {
670
- new ctor(props, context); // eslint-disable-line no-new
667
+ instance = new ctor(props, context); // eslint-disable-line no-new
668
} finally {
669
reenableLogs();
670
}
671
}
672
}
673
677
- const instance = new ctor(props, context);
674
const state = (workInProgress.memoizedState =
675
instance.state !== null && instance.state !== undefined
676
? instance.state
packages/react-reconciler/src/ReactFiberClassComponent.old.js
+14
-18
@@ -169,7 +169,7 @@ function applyDerivedStateFromProps(
169
nextProps: any,
170
) {
171
const prevState = workInProgress.memoizedState;
172
-
172
+ let partialState = getDerivedStateFromProps(nextProps, prevState);
173
if (__DEV__) {
174
if (
175
debugRenderPhaseSideEffectsForStrictMode &&
@@ -178,16 +178,11 @@ function applyDerivedStateFromProps(
178
disableLogs();
179
try {
180
// Invoke the function an extra time to help detect side-effects.
181
- getDerivedStateFromProps(nextProps, prevState);
181
+ partialState = getDerivedStateFromProps(nextProps, prevState);
182
} finally {
183
reenableLogs();
184
}
185
}
186
- }
187
-
188
- const partialState = getDerivedStateFromProps(nextProps, prevState);
189
-
190
- if (__DEV__) {
186
warnOnUndefinedDerivedState(ctor, partialState);
187
}
188
// Merge the partial state and the previous state.
@@ -323,6 +318,11 @@ function checkShouldComponentUpdate(
318
) {
319
const instance = workInProgress.stateNode;
320
if (typeof instance.shouldComponentUpdate === 'function') {
321
+ let shouldUpdate = instance.shouldComponentUpdate(
322
+ newProps,
323
+ newState,
324
+ nextContext,
325
+ );
326
if (__DEV__) {
327
if (
328
debugRenderPhaseSideEffectsForStrictMode &&
@@ -331,19 +331,15 @@ function checkShouldComponentUpdate(
331
disableLogs();
332
try {
333
// Invoke the function an extra time to help detect side-effects.
334
- instance.shouldComponentUpdate(newProps, newState, nextContext);
334
+ shouldUpdate = instance.shouldComponentUpdate(
335
+ newProps,
336
+ newState,
337
+ nextContext,
338
+ );
339
} finally {
340
reenableLogs();
341
}
342
}
339
- }
340
- const shouldUpdate = instance.shouldComponentUpdate(
341
- newProps,
342
- newState,
343
- nextContext,
344
- );
345
-
346
- if (__DEV__) {
343
if (shouldUpdate === undefined) {
344
console.error(
345
'%s.shouldComponentUpdate(): Returned undefined instead of a ' +
@@ -659,6 +655,7 @@ function constructClassInstance(
655
: emptyContextObject;
656
}
657
658
+ let instance = new ctor(props, context);
659
// Instantiate twice to help detect side-effects.
660
if (__DEV__) {
661
if (
@@ -667,14 +664,13 @@ function constructClassInstance(
664
) {
665
disableLogs();
666
try {
670
- new ctor(props, context); // eslint-disable-line no-new
667
+ instance = new ctor(props, context); // eslint-disable-line no-new
668
} finally {
669
reenableLogs();
670
}
671
}
672
}
673
677
- const instance = new ctor(props, context);
674
const state = (workInProgress.memoizedState =
675
instance.state !== null && instance.state !== undefined
676
? instance.state
packages/react/src/__tests__/ReactStrictMode-test.js
+192
@@ -892,4 +892,196 @@ describe('context legacy', () => {
892
// Dedupe
893
ReactDOM.render(<Root />, container);
894
});
895
+
896
+ describe('disableLogs', () => {
897
+ it('disables logs once for class double render', () => {
898
+ spyOnDevAndProd(console, 'log');
899
+
900
+ let count = 0;
901
+ class Foo extends React.Component {
902
+ render() {
903
+ count++;
904
+ console.log('foo ' + count);
905
+ return null;
906
+ }
907
+ }
908
+
909
+ const container = document.createElement('div');
910
+ ReactDOM.render(
911
+ <React.StrictMode>
912
+ <Foo />
913
+ </React.StrictMode>,
914
+ container,
915
+ );
916
+
917
+ expect(count).toBe(__DEV__ ? 2 : 1);
918
+ expect(console.log).toBeCalledTimes(1);
919
+ // Note: we should display the first log because otherwise
920
+ // there is a risk of suppressing warnings when they happen,
921
+ // and on the next render they'd get deduplicated and ignored.
922
+ expect(console.log).toBeCalledWith('foo 1');
923
+ });
924
+
925
+ it('disables logs once for class double ctor', () => {
926
+ spyOnDevAndProd(console, 'log');
927
+
928
+ let count = 0;
929
+ class Foo extends React.Component {
930
+ constructor(props) {
931
+ super(props);
932
+ count++;
933
+ console.log('foo ' + count);
934
+ }
935
+ render() {
936
+ return null;
937
+ }
938
+ }
939
+
940
+ const container = document.createElement('div');
941
+ ReactDOM.render(
942
+ <React.StrictMode>
943
+ <Foo />
944
+ </React.StrictMode>,
945
+ container,
946
+ );
947
+
948
+ expect(count).toBe(__DEV__ ? 2 : 1);
949
+ expect(console.log).toBeCalledTimes(1);
950
+ // Note: we should display the first log because otherwise
951
+ // there is a risk of suppressing warnings when they happen,
952
+ // and on the next render they'd get deduplicated and ignored.
953
+ expect(console.log).toBeCalledWith('foo 1');
954
+ });
955
+
956
+ it('disables logs once for class double getDerivedStateFromProps', () => {
957
+ spyOnDevAndProd(console, 'log');
958
+
959
+ let count = 0;
960
+ class Foo extends React.Component {
961
+ state = {};
962
+ static getDerivedStateFromProps() {
963
+ count++;
964
+ console.log('foo ' + count);
965
+ return {};
966
+ }
967
+ render() {
968
+ return null;
969
+ }
970
+ }
971
+
972
+ const container = document.createElement('div');
973
+ ReactDOM.render(
974
+ <React.StrictMode>
975
+ <Foo />
976
+ </React.StrictMode>,
977
+ container,
978
+ );
979
+
980
+ expect(count).toBe(__DEV__ ? 2 : 1);
981
+ expect(console.log).toBeCalledTimes(1);
982
+ // Note: we should display the first log because otherwise
983
+ // there is a risk of suppressing warnings when they happen,
984
+ // and on the next render they'd get deduplicated and ignored.
985
+ expect(console.log).toBeCalledWith('foo 1');
986
+ });
987
+
988
+ it('disables logs once for class double shouldComponentUpdate', () => {
989
+ spyOnDevAndProd(console, 'log');
990
+
991
+ let count = 0;
992
+ class Foo extends React.Component {
993
+ state = {};
994
+ shouldComponentUpdate() {
995
+ count++;
996
+ console.log('foo ' + count);
997
+ return {};
998
+ }
999
+ render() {
1000
+ return null;
1001
+ }
1002
+ }
1003
+
1004
+ const container = document.createElement('div');
1005
+ ReactDOM.render(
1006
+ <React.StrictMode>
1007
+ <Foo />
1008
+ </React.StrictMode>,
1009
+ container,
1010
+ );
1011
+ // Trigger sCU:
1012
+ ReactDOM.render(
1013
+ <React.StrictMode>
1014
+ <Foo />
1015
+ </React.StrictMode>,
1016
+ container,
1017
+ );
1018
+
1019
+ expect(count).toBe(__DEV__ ? 2 : 1);
1020
+ expect(console.log).toBeCalledTimes(1);
1021
+ // Note: we should display the first log because otherwise
1022
+ // there is a risk of suppressing warnings when they happen,
1023
+ // and on the next render they'd get deduplicated and ignored.
1024
+ expect(console.log).toBeCalledWith('foo 1');
1025
+ });
1026
+
1027
+ it('disables logs once for class state updaters', () => {
1028
+ spyOnDevAndProd(console, 'log');
1029
+
1030
+ let inst;
1031
+ let count = 0;
1032
+ class Foo extends React.Component {
1033
+ state = {};
1034
+ render() {
1035
+ inst = this;
1036
+ return null;
1037
+ }
1038
+ }
1039
+
1040
+ const container = document.createElement('div');
1041
+ ReactDOM.render(
1042
+ <React.StrictMode>
1043
+ <Foo />
1044
+ </React.StrictMode>,
1045
+ container,
1046
+ );
1047
+ inst.setState(() => {
1048
+ count++;
1049
+ console.log('foo ' + count);
1050
+ return {};
1051
+ });
1052
+
1053
+ expect(count).toBe(__DEV__ ? 2 : 1);
1054
+ expect(console.log).toBeCalledTimes(1);
1055
+ // Note: we should display the first log because otherwise
1056
+ // there is a risk of suppressing warnings when they happen,
1057
+ // and on the next render they'd get deduplicated and ignored.
1058
+ expect(console.log).toBeCalledWith('foo 1');
1059
+ });
1060
+
1061
+ it('disables logs once for function double render', () => {
1062
+ spyOnDevAndProd(console, 'log');
1063
+
1064
+ let count = 0;
1065
+ function Foo() {
1066
+ count++;
1067
+ console.log('foo ' + count);
1068
+ return null;
1069
+ }
1070
+
1071
+ const container = document.createElement('div');
1072
+ ReactDOM.render(
1073
+ <React.StrictMode>
1074
+ <Foo />
1075
+ </React.StrictMode>,
1076
+ container,
1077
+ );
1078
+
1079
+ expect(count).toBe(__DEV__ ? 2 : 1);
1080
+ expect(console.log).toBeCalledTimes(1);
1081
+ // Note: we should display the first log because otherwise
1082
+ // there is a risk of suppressing warnings when they happen,
1083
+ // and on the next render they'd get deduplicated and ignored.
1084
+ expect(console.log).toBeCalledWith('foo 1');
1085
+ });
1086
+ });
1087
});