Skip to content

Commit f945a02

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

File tree

2 files changed

+273
-0
lines changed

2 files changed

+273
-0
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 53 additions & 0 deletions
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

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,4 +699,224 @@ describe('ReactHooks', () => {
699699
'Hooks can only be called inside the body of a function component',
700700
);
701701
});
702+
703+
it('double-invokes components with Hooks in Strict Mode', () => {
704+
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true;
705+
706+
const {useState, StrictMode} = React;
707+
let renderCount = 0;
708+
709+
function NoHooks() {
710+
renderCount++;
711+
return <div />;
712+
}
713+
714+
function HasHooks() {
715+
useState(0);
716+
renderCount++;
717+
return <div />;
718+
}
719+
720+
const FwdRef = React.forwardRef((props, ref) => {
721+
renderCount++;
722+
return <div />;
723+
});
724+
725+
const FwdRefHasHooks = React.forwardRef((props, ref) => {
726+
useState(0);
727+
renderCount++;
728+
return <div />;
729+
});
730+
731+
const Memo = React.memo(props => {
732+
renderCount++;
733+
return <div />;
734+
});
735+
736+
const MemoHasHooks = React.memo(props => {
737+
useState(0);
738+
renderCount++;
739+
return <div />;
740+
});
741+
742+
function Factory() {
743+
return {
744+
state: {},
745+
render() {
746+
renderCount++;
747+
return <div />;
748+
},
749+
};
750+
}
751+
752+
let renderer = ReactTestRenderer.create(null);
753+
754+
renderCount = 0;
755+
renderer.update(<NoHooks />);
756+
expect(renderCount).toBe(1);
757+
renderCount = 0;
758+
renderer.update(<NoHooks />);
759+
expect(renderCount).toBe(1);
760+
renderCount = 0;
761+
renderer.update(
762+
<StrictMode>
763+
<NoHooks />
764+
</StrictMode>,
765+
);
766+
expect(renderCount).toBe(1);
767+
renderCount = 0;
768+
renderer.update(
769+
<StrictMode>
770+
<NoHooks />
771+
</StrictMode>,
772+
);
773+
expect(renderCount).toBe(1);
774+
775+
renderCount = 0;
776+
renderer.update(<FwdRef />);
777+
expect(renderCount).toBe(1);
778+
renderCount = 0;
779+
renderer.update(<FwdRef />);
780+
expect(renderCount).toBe(1);
781+
renderCount = 0;
782+
renderer.update(
783+
<StrictMode>
784+
<FwdRef />
785+
</StrictMode>,
786+
);
787+
expect(renderCount).toBe(1);
788+
renderCount = 0;
789+
renderer.update(
790+
<StrictMode>
791+
<FwdRef />
792+
</StrictMode>,
793+
);
794+
expect(renderCount).toBe(1);
795+
796+
renderCount = 0;
797+
renderer.update(<Memo arg={1} />);
798+
expect(renderCount).toBe(1);
799+
renderCount = 0;
800+
renderer.update(<Memo arg={2} />);
801+
expect(renderCount).toBe(1);
802+
renderCount = 0;
803+
renderer.update(
804+
<StrictMode>
805+
<Memo arg={1} />
806+
</StrictMode>,
807+
);
808+
expect(renderCount).toBe(1);
809+
renderCount = 0;
810+
renderer.update(
811+
<StrictMode>
812+
<Memo arg={2} />
813+
</StrictMode>,
814+
);
815+
expect(renderCount).toBe(1);
816+
817+
renderCount = 0;
818+
renderer.update(<Factory />);
819+
expect(renderCount).toBe(1);
820+
renderCount = 0;
821+
renderer.update(<Factory />);
822+
expect(renderCount).toBe(1);
823+
renderCount = 0;
824+
renderer.update(
825+
<StrictMode>
826+
<Factory />
827+
</StrictMode>,
828+
);
829+
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
830+
renderCount = 0;
831+
renderer.update(
832+
<StrictMode>
833+
<Factory />
834+
</StrictMode>,
835+
);
836+
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
837+
838+
renderCount = 0;
839+
renderer.update(<HasHooks />);
840+
expect(renderCount).toBe(1);
841+
renderCount = 0;
842+
renderer.update(<HasHooks />);
843+
expect(renderCount).toBe(1);
844+
renderCount = 0;
845+
renderer.update(
846+
<StrictMode>
847+
<HasHooks />
848+
</StrictMode>,
849+
);
850+
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
851+
renderCount = 0;
852+
renderer.update(
853+
<StrictMode>
854+
<HasHooks />
855+
</StrictMode>,
856+
);
857+
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
858+
859+
renderCount = 0;
860+
renderer.update(<FwdRefHasHooks />);
861+
expect(renderCount).toBe(1);
862+
renderCount = 0;
863+
renderer.update(<FwdRefHasHooks />);
864+
expect(renderCount).toBe(1);
865+
renderCount = 0;
866+
renderer.update(
867+
<StrictMode>
868+
<FwdRefHasHooks />
869+
</StrictMode>,
870+
);
871+
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
872+
renderCount = 0;
873+
renderer.update(
874+
<StrictMode>
875+
<FwdRefHasHooks />
876+
</StrictMode>,
877+
);
878+
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
879+
880+
renderCount = 0;
881+
renderer.update(<MemoHasHooks arg={1} />);
882+
expect(renderCount).toBe(1);
883+
renderCount = 0;
884+
renderer.update(<MemoHasHooks arg={2} />);
885+
expect(renderCount).toBe(1);
886+
renderCount = 0;
887+
renderer.update(
888+
<StrictMode>
889+
<MemoHasHooks arg={1} />
890+
</StrictMode>,
891+
);
892+
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
893+
renderCount = 0;
894+
renderer.update(
895+
<StrictMode>
896+
<MemoHasHooks arg={2} />
897+
</StrictMode>,
898+
);
899+
expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
900+
});
901+
902+
it('double-invokes useMemo in DEV StrictMode despite []', () => {
903+
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true;
904+
const {useMemo, StrictMode} = React;
905+
906+
let useMemoCount = 0;
907+
function BadUseMemo() {
908+
useMemo(() => {
909+
useMemoCount++;
910+
}, []);
911+
return <div />;
912+
}
913+
914+
useMemoCount = 0;
915+
ReactTestRenderer.create(
916+
<StrictMode>
917+
<BadUseMemo />
918+
</StrictMode>,
919+
);
920+
expect(useMemoCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
921+
});
702922
});

0 commit comments

Comments
 (0)