Skip to content

Commit 73962c3

Browse files
authored
Revert "Revert "Double-render function components with Hooks in DEV in StrictMode" (#14652)" (#14654)
This reverts commit 3fbebb2.
1 parent 9944392 commit 73962c3

File tree

2 files changed

+273
-0
lines changed

2 files changed

+273
-0
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

+53
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,23 @@ function updateForwardRef(
250250
ref,
251251
renderExpirationTime,
252252
);
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+
}
253270
setCurrentPhase(null);
254271
} else {
255272
nextChildren = renderWithHooks(
@@ -543,6 +560,23 @@ function updateFunctionComponent(
543560
context,
544561
renderExpirationTime,
545562
);
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+
}
546580
setCurrentPhase(null);
547581
} else {
548582
nextChildren = renderWithHooks(
@@ -1210,6 +1244,25 @@ function mountIndeterminateComponent(
12101244
} else {
12111245
// Proceed under the assumption that this is a function component
12121246
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+
}
12131266
reconcileChildren(null, workInProgress, value, renderExpirationTime);
12141267
if (__DEV__) {
12151268
validateFunctionComponentInDev(workInProgress, Component);

packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js

+220
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,226 @@ describe('ReactHooks', () => {
841841
);
842842
});
843843

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+
8441064
it('warns on using differently ordered hooks on subsequent renders', () => {
8451065
const {useState, useReducer} = React;
8461066
function useCustomHook() {

0 commit comments

Comments
 (0)