Revert "Revert "Double-render function components with Hooks in DEV in StrictMode" (#14652)" (#14654)
This reverts commit 3fbebb2a0b3b806f428c4154700af8e75e561895.
Dan Abramov committed
Jan 23, 2019 at 19:12 UTC
73962c36645171a7a897bb0fb58e3442d0bb6bc9
2 files changed
+273
packages/react-reconciler/src/ReactFiberBeginWork.js
+53
@@ -250,6 +250,23 @@ function updateForwardRef(
250
ref,
251
renderExpirationTime,
252
);
253
+ if (
254
+ debugRenderPhaseSideEffects ||
255
+ (debugRenderPhaseSideEffectsForStrictMode &&
256
+ workInProgress.mode & StrictMode)
257
+ ) {
258
+ // Only double-render components with Hooks
259
+ if (workInProgress.memoizedState !== null) {
260
+ renderWithHooks(
261
+ current,
262
+ workInProgress,
263
+ render,
264
+ nextProps,
265
+ ref,
266
+ renderExpirationTime,
267
+ );
268
+ }
269
+ }
270
setCurrentPhase(null);
271
} else {
272
nextChildren = renderWithHooks(
@@ -543,6 +560,23 @@ function updateFunctionComponent(
560
context,
561
renderExpirationTime,
562
);
563
+ if (
564
+ debugRenderPhaseSideEffects ||
565
+ (debugRenderPhaseSideEffectsForStrictMode &&
566
+ workInProgress.mode & StrictMode)
567
+ ) {
568
+ // Only double-render components with Hooks
569
+ if (workInProgress.memoizedState !== null) {
570
+ renderWithHooks(
571
+ current,
572
+ workInProgress,
573
+ Component,
574
+ nextProps,
575
+ context,
576
+ renderExpirationTime,
577
+ );
578
+ }
579
+ }
580
setCurrentPhase(null);
581
} else {
582
nextChildren = renderWithHooks(
@@ -1210,6 +1244,25 @@ function mountIndeterminateComponent(
1244
} else {
1245
// Proceed under the assumption that this is a function component
1246
workInProgress.tag = FunctionComponent;
1247
+ if (__DEV__) {
1248
+ if (
1249
+ debugRenderPhaseSideEffects ||
1250
+ (debugRenderPhaseSideEffectsForStrictMode &&
1251
+ workInProgress.mode & StrictMode)
1252
+ ) {
1253
+ // Only double-render components with Hooks
1254
+ if (workInProgress.memoizedState !== null) {
1255
+ renderWithHooks(
1256
+ null,
1257
+ workInProgress,
1258
+ Component,
1259
+ props,
1260
+ context,
1261
+ renderExpirationTime,
1262
+ );
1263
+ }
1264
+ }
1265
+ }
1266
reconcileChildren(null, workInProgress, value, renderExpirationTime);
1267
if (__DEV__) {
1268
validateFunctionComponentInDev(workInProgress, Component);
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+220
@@ -841,6 +841,226 @@ describe('ReactHooks', () => {
841
);
842
});
843
844
+ it('double-invokes components with Hooks in Strict Mode', () => {
845
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true;
846
+
847
+ const {useState, StrictMode} = React;
848
+ let renderCount = 0;
849
+
850
+ function NoHooks() {
851
+ renderCount++;
852
+ return <div />;
853
+ }
854
+
855
+ function HasHooks() {
856
+ useState(0);
857
+ renderCount++;
858
+ return <div />;
859
+ }
860
+
861
+ const FwdRef = React.forwardRef((props, ref) => {
862
+ renderCount++;
863
+ return <div />;
864
+ });
865
+
866
+ const FwdRefHasHooks = React.forwardRef((props, ref) => {
867
+ useState(0);
868
+ renderCount++;
869
+ return <div />;
870
+ });
871
+
872
+ const Memo = React.memo(props => {
873
+ renderCount++;
874
+ return <div />;
875
+ });
876
+
877
+ const MemoHasHooks = React.memo(props => {
878
+ useState(0);
879
+ renderCount++;
880
+ return <div />;
881
+ });
882
+
883
+ function Factory() {
884
+ return {
885
+ state: {},
886
+ render() {
887
+ renderCount++;
888
+ return <div />;
889
+ },
890
+ };
891
+ }
892
+
893
+ let renderer = ReactTestRenderer.create(null);
894
+
895
+ renderCount = 0;
896
+ renderer.update(<NoHooks />);
897
+ expect(renderCount).toBe(1);
898
+ renderCount = 0;
899
+ renderer.update(<NoHooks />);
900
+ expect(renderCount).toBe(1);
901
+ renderCount = 0;
902
+ renderer.update(
903
+ <StrictMode>
904
+ <NoHooks />
905
+ </StrictMode>,
906
+ );
907
+ expect(renderCount).toBe(1);
908
+ renderCount = 0;
909
+ renderer.update(
910
+ <StrictMode>
911
+ <NoHooks />
912
+ </StrictMode>,
913
+ );
914
+ expect(renderCount).toBe(1);
915
+
916
+ renderCount = 0;
917
+ renderer.update(<FwdRef />);
918
+ expect(renderCount).toBe(1);
919
+ renderCount = 0;
920
+ renderer.update(<FwdRef />);
921
+ expect(renderCount).toBe(1);
922
+ renderCount = 0;
923
+ renderer.update(
924
+ <StrictMode>
925
+ <FwdRef />
926
+ </StrictMode>,
927
+ );
928
+ expect(renderCount).toBe(1);
929
+ renderCount = 0;
930
+ renderer.update(
931
+ <StrictMode>
932
+ <FwdRef />
933
+ </StrictMode>,
934
+ );
935
+ expect(renderCount).toBe(1);
936
+
937
+ renderCount = 0;
938
+ renderer.update(<Memo arg={1} />);
939
+ expect(renderCount).toBe(1);
940
+ renderCount = 0;
941
+ renderer.update(<Memo arg={2} />);
942
+ expect(renderCount).toBe(1);
943
+ renderCount = 0;
944
+ renderer.update(
945
+ <StrictMode>
946
+ <Memo arg={1} />
947
+ </StrictMode>,
948
+ );
949
+ expect(renderCount).toBe(1);
950
+ renderCount = 0;
951
+ renderer.update(
952
+ <StrictMode>
953
+ <Memo arg={2} />
954
+ </StrictMode>,
955
+ );
956
+ expect(renderCount).toBe(1);
957
+
958
+ renderCount = 0;
959
+ renderer.update(<Factory />);
960
+ expect(renderCount).toBe(1);
961
+ renderCount = 0;
962
+ renderer.update(<Factory />);
963
+ expect(renderCount).toBe(1);
964
+ renderCount = 0;
965
+ renderer.update(
966
+ <StrictMode>
967
+ <Factory />
968
+ </StrictMode>,
969
+ );
970
+ expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
971
+ renderCount = 0;
972
+ renderer.update(
973
+ <StrictMode>
974
+ <Factory />
975
+ </StrictMode>,
976
+ );
977
+ expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
978
+
979
+ renderCount = 0;
980
+ renderer.update(<HasHooks />);
981
+ expect(renderCount).toBe(1);
982
+ renderCount = 0;
983
+ renderer.update(<HasHooks />);
984
+ expect(renderCount).toBe(1);
985
+ renderCount = 0;
986
+ renderer.update(
987
+ <StrictMode>
988
+ <HasHooks />
989
+ </StrictMode>,
990
+ );
991
+ expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
992
+ renderCount = 0;
993
+ renderer.update(
994
+ <StrictMode>
995
+ <HasHooks />
996
+ </StrictMode>,
997
+ );
998
+ expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
999
+
1000
+ renderCount = 0;
1001
+ renderer.update(<FwdRefHasHooks />);
1002
+ expect(renderCount).toBe(1);
1003
+ renderCount = 0;
1004
+ renderer.update(<FwdRefHasHooks />);
1005
+ expect(renderCount).toBe(1);
1006
+ renderCount = 0;
1007
+ renderer.update(
1008
+ <StrictMode>
1009
+ <FwdRefHasHooks />
1010
+ </StrictMode>,
1011
+ );
1012
+ expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1013
+ renderCount = 0;
1014
+ renderer.update(
1015
+ <StrictMode>
1016
+ <FwdRefHasHooks />
1017
+ </StrictMode>,
1018
+ );
1019
+ expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1020
+
1021
+ renderCount = 0;
1022
+ renderer.update(<MemoHasHooks arg={1} />);
1023
+ expect(renderCount).toBe(1);
1024
+ renderCount = 0;
1025
+ renderer.update(<MemoHasHooks arg={2} />);
1026
+ expect(renderCount).toBe(1);
1027
+ renderCount = 0;
1028
+ renderer.update(
1029
+ <StrictMode>
1030
+ <MemoHasHooks arg={1} />
1031
+ </StrictMode>,
1032
+ );
1033
+ expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1034
+ renderCount = 0;
1035
+ renderer.update(
1036
+ <StrictMode>
1037
+ <MemoHasHooks arg={2} />
1038
+ </StrictMode>,
1039
+ );
1040
+ expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1041
+ });
1042
+
1043
+ it('double-invokes useMemo in DEV StrictMode despite []', () => {
1044
+ ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true;
1045
+ const {useMemo, StrictMode} = React;
1046
+
1047
+ let useMemoCount = 0;
1048
+ function BadUseMemo() {
1049
+ useMemo(() => {
1050
+ useMemoCount++;
1051
+ }, []);
1052
+ return <div />;
1053
+ }
1054
+
1055
+ useMemoCount = 0;
1056
+ ReactTestRenderer.create(
1057
+ <StrictMode>
1058
+ <BadUseMemo />
1059
+ </StrictMode>,
1060
+ );
1061
+ expect(useMemoCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
1062
+ });
1063
+
1064
it('warns on using differently ordered hooks on subsequent renders', () => {
1065
const {useState, useReducer} = React;
1066
function useCustomHook() {