From 73962c36645171a7a897bb0fb58e3442d0bb6bc9 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 23 Jan 2019 19:12:18 +0000 Subject: [PATCH] Revert "Revert "Double-render function components with Hooks in DEV in StrictMode" (#14652)" (#14654) This reverts commit 3fbebb2a0b3b806f428c4154700af8e75e561895. --- .../src/ReactFiberBeginWork.js | 53 +++++ .../src/__tests__/ReactHooks-test.internal.js | 220 ++++++++++++++++++ 2 files changed, 273 insertions(+) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 5fc0fcc871426..9c4a72b7fedcb 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -250,6 +250,23 @@ function updateForwardRef( ref, renderExpirationTime, ); + if ( + debugRenderPhaseSideEffects || + (debugRenderPhaseSideEffectsForStrictMode && + workInProgress.mode & StrictMode) + ) { + // Only double-render components with Hooks + if (workInProgress.memoizedState !== null) { + renderWithHooks( + current, + workInProgress, + render, + nextProps, + ref, + renderExpirationTime, + ); + } + } setCurrentPhase(null); } else { nextChildren = renderWithHooks( @@ -543,6 +560,23 @@ function updateFunctionComponent( context, renderExpirationTime, ); + if ( + debugRenderPhaseSideEffects || + (debugRenderPhaseSideEffectsForStrictMode && + workInProgress.mode & StrictMode) + ) { + // Only double-render components with Hooks + if (workInProgress.memoizedState !== null) { + renderWithHooks( + current, + workInProgress, + Component, + nextProps, + context, + renderExpirationTime, + ); + } + } setCurrentPhase(null); } else { nextChildren = renderWithHooks( @@ -1210,6 +1244,25 @@ function mountIndeterminateComponent( } else { // Proceed under the assumption that this is a function component workInProgress.tag = FunctionComponent; + if (__DEV__) { + if ( + debugRenderPhaseSideEffects || + (debugRenderPhaseSideEffectsForStrictMode && + workInProgress.mode & StrictMode) + ) { + // Only double-render components with Hooks + if (workInProgress.memoizedState !== null) { + renderWithHooks( + null, + workInProgress, + Component, + props, + context, + renderExpirationTime, + ); + } + } + } reconcileChildren(null, workInProgress, value, renderExpirationTime); if (__DEV__) { validateFunctionComponentInDev(workInProgress, Component); diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js index bf4483986313e..e7c43ce274233 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js @@ -841,6 +841,226 @@ describe('ReactHooks', () => { ); }); + it('double-invokes components with Hooks in Strict Mode', () => { + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true; + + const {useState, StrictMode} = React; + let renderCount = 0; + + function NoHooks() { + renderCount++; + return
; + } + + function HasHooks() { + useState(0); + renderCount++; + return
; + } + + const FwdRef = React.forwardRef((props, ref) => { + renderCount++; + return
; + }); + + const FwdRefHasHooks = React.forwardRef((props, ref) => { + useState(0); + renderCount++; + return
; + }); + + const Memo = React.memo(props => { + renderCount++; + return
; + }); + + const MemoHasHooks = React.memo(props => { + useState(0); + renderCount++; + return
; + }); + + function Factory() { + return { + state: {}, + render() { + renderCount++; + return
; + }, + }; + } + + let renderer = ReactTestRenderer.create(null); + + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(1); + + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(1); + + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(1); + + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Treated like a class + + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks + + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks + + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update(); + expect(renderCount).toBe(1); + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks + renderCount = 0; + renderer.update( + + + , + ); + expect(renderCount).toBe(__DEV__ ? 2 : 1); // Has Hooks + }); + + it('double-invokes useMemo in DEV StrictMode despite []', () => { + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = true; + const {useMemo, StrictMode} = React; + + let useMemoCount = 0; + function BadUseMemo() { + useMemo(() => { + useMemoCount++; + }, []); + return
; + } + + useMemoCount = 0; + ReactTestRenderer.create( + + + , + ); + expect(useMemoCount).toBe(__DEV__ ? 2 : 1); // Has Hooks + }); + it('warns on using differently ordered hooks on subsequent renders', () => { const {useState, useReducer} = React; function useCustomHook() {