@samitouri / QOS-React-2 / commits / 51b0becf3e

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