@samitouri / QOS-React / commits / a1414e8949

Double-render function components with Hooks in DEV in StrictMode (#14643)

* Double-render functions in strict mode * Double-invoke first function component render too * Mark TestRendererAsync test as internal and revert changes to it TestRenderer is built with strict mode doublerender off. We could change that but I'm not sure we want to. So I'll just flip the flag off for this test. * Only double-invoke components using Hooks * Revert unintentional change

Dan Abramov committed Jan 21, 2019 at 15:35 UTC a1414e8949915c006bba411fa2949b304dd044ae
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(
@@ -1207,6 +1241,25 @@ function mountIndeterminateComponent(
1241 } else {
1242 // Proceed under the assumption that this is a function component
1243 workInProgress.tag = FunctionComponent;
1244 + if (__DEV__) {
1245 + if (
1246 + debugRenderPhaseSideEffects ||
1247 + (debugRenderPhaseSideEffectsForStrictMode &&
1248 + workInProgress.mode & StrictMode)
1249 + ) {
1250 + // Only double-render components with Hooks
1251 + if (workInProgress.memoizedState !== null) {
1252 + renderWithHooks(
1253 + null,
1254 + workInProgress,
1255 + Component,
1256 + props,
1257 + context,
1258 + renderExpirationTime,
1259 + );
1260 + }
1261 + }
1262 + }
1263 reconcileChildren(null, workInProgress, value, renderExpirationTime);
1264 if (__DEV__) {
1265 validateFunctionComponentInDev(workInProgress, Component);
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+220
@@ -660,4 +660,224 @@ describe('ReactHooks', () => {
660 'Hooks can only be called inside the body of a function component',
661 );
662 });
663 +
664 + it('double-invokes components with Hooks in Strict Mode', () => {
665 + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true;
666 +
667 + const {useState, StrictMode} = React;
668 + let renderCount = 0;
669 +
670 + function NoHooks() {
671 + renderCount++;
672 + return <div />;
673 + }
674 +
675 + function HasHooks() {
676 + useState(0);
677 + renderCount++;
678 + return <div />;
679 + }
680 +
681 + const FwdRef = React.forwardRef((props, ref) => {
682 + renderCount++;
683 + return <div />;
684 + });
685 +
686 + const FwdRefHasHooks = React.forwardRef((props, ref) => {
687 + useState(0);
688 + renderCount++;
689 + return <div />;
690 + });
691 +
692 + const Memo = React.memo(props => {
693 + renderCount++;
694 + return <div />;
695 + });
696 +
697 + const MemoHasHooks = React.memo(props => {
698 + useState(0);
699 + renderCount++;
700 + return <div />;
701 + });
702 +
703 + function Factory() {
704 + return {
705 + state: {},
706 + render() {
707 + renderCount++;
708 + return <div />;
709 + },
710 + };
711 + }
712 +
713 + let renderer = ReactTestRenderer.create(null);
714 +
715 + renderCount = 0;
716 + renderer.update(<NoHooks />);
717 + expect(renderCount).toBe(1);
718 + renderCount = 0;
719 + renderer.update(<NoHooks />);
720 + expect(renderCount).toBe(1);
721 + renderCount = 0;
722 + renderer.update(
723 + <StrictMode>
724 + <NoHooks />
725 + </StrictMode>,
726 + );
727 + expect(renderCount).toBe(1);
728 + renderCount = 0;
729 + renderer.update(
730 + <StrictMode>
731 + <NoHooks />
732 + </StrictMode>,
733 + );
734 + expect(renderCount).toBe(1);
735 +
736 + renderCount = 0;
737 + renderer.update(<FwdRef />);
738 + expect(renderCount).toBe(1);
739 + renderCount = 0;
740 + renderer.update(<FwdRef />);
741 + expect(renderCount).toBe(1);
742 + renderCount = 0;
743 + renderer.update(
744 + <StrictMode>
745 + <FwdRef />
746 + </StrictMode>,
747 + );
748 + expect(renderCount).toBe(1);
749 + renderCount = 0;
750 + renderer.update(
751 + <StrictMode>
752 + <FwdRef />
753 + </StrictMode>,
754 + );
755 + expect(renderCount).toBe(1);
756 +
757 + renderCount = 0;
758 + renderer.update(<Memo arg={1} />);
759 + expect(renderCount).toBe(1);
760 + renderCount = 0;
761 + renderer.update(<Memo arg={2} />);
762 + expect(renderCount).toBe(1);
763 + renderCount = 0;
764 + renderer.update(
765 + <StrictMode>
766 + <Memo arg={1} />
767 + </StrictMode>,
768 + );
769 + expect(renderCount).toBe(1);
770 + renderCount = 0;
771 + renderer.update(
772 + <StrictMode>
773 + <Memo arg={2} />
774 + </StrictMode>,
775 + );
776 + expect(renderCount).toBe(1);
777 +
778 + renderCount = 0;
779 + renderer.update(<Factory />);
780 + expect(renderCount).toBe(1);
781 + renderCount = 0;
782 + renderer.update(<Factory />);
783 + expect(renderCount).toBe(1);
784 + renderCount = 0;
785 + renderer.update(
786 + <StrictMode>
787 + <Factory />
788 + </StrictMode>,
789 + );
790 + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
791 + renderCount = 0;
792 + renderer.update(
793 + <StrictMode>
794 + <Factory />
795 + </StrictMode>,
796 + );
797 + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class
798 +
799 + renderCount = 0;
800 + renderer.update(<HasHooks />);
801 + expect(renderCount).toBe(1);
802 + renderCount = 0;
803 + renderer.update(<HasHooks />);
804 + expect(renderCount).toBe(1);
805 + renderCount = 0;
806 + renderer.update(
807 + <StrictMode>
808 + <HasHooks />
809 + </StrictMode>,
810 + );
811 + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
812 + renderCount = 0;
813 + renderer.update(
814 + <StrictMode>
815 + <HasHooks />
816 + </StrictMode>,
817 + );
818 + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
819 +
820 + renderCount = 0;
821 + renderer.update(<FwdRefHasHooks />);
822 + expect(renderCount).toBe(1);
823 + renderCount = 0;
824 + renderer.update(<FwdRefHasHooks />);
825 + expect(renderCount).toBe(1);
826 + renderCount = 0;
827 + renderer.update(
828 + <StrictMode>
829 + <FwdRefHasHooks />
830 + </StrictMode>,
831 + );
832 + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
833 + renderCount = 0;
834 + renderer.update(
835 + <StrictMode>
836 + <FwdRefHasHooks />
837 + </StrictMode>,
838 + );
839 + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
840 +
841 + renderCount = 0;
842 + renderer.update(<MemoHasHooks arg={1} />);
843 + expect(renderCount).toBe(1);
844 + renderCount = 0;
845 + renderer.update(<MemoHasHooks arg={2} />);
846 + expect(renderCount).toBe(1);
847 + renderCount = 0;
848 + renderer.update(
849 + <StrictMode>
850 + <MemoHasHooks arg={1} />
851 + </StrictMode>,
852 + );
853 + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
854 + renderCount = 0;
855 + renderer.update(
856 + <StrictMode>
857 + <MemoHasHooks arg={2} />
858 + </StrictMode>,
859 + );
860 + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
861 + });
862 +
863 + it('double-invokes useMemo in DEV StrictMode despite []', () => {
864 + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true;
865 + const {useMemo, StrictMode} = React;
866 +
867 + let useMemoCount = 0;
868 + function BadUseMemo() {
869 + useMemo(() => {
870 + useMemoCount++;
871 + }, []);
872 + return <div />;
873 + }
874 +
875 + useMemoCount = 0;
876 + ReactTestRenderer.create(
877 + <StrictMode>
878 + <BadUseMemo />
879 + </StrictMode>,
880 + );
881 + expect(useMemoCount).toBe(__DEV__ ? 2 : 1); // Has Hooks
882 + });
883 });