From ef3267721db4f8c1023fb887f23c6850c01859f9 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 19 Oct 2018 20:31:44 -0700 Subject: [PATCH] Always bail out timed out children even if they receive an update (#13901) * Always bail out timed out children even if they receive an update The fragment that wraps timed-out children should always have an expiration time of NoWork. * Don't need to set expirationTime, only childExpirationTime --- .../src/ReactFiberBeginWork.js | 3 ++ .../__tests__/ReactSuspense-test.internal.js | 48 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index c51869ab659ef..69a101971a515 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -1080,6 +1080,7 @@ function updateSuspenseComponent( )); fallbackChildFragment.effectTag |= Placement; child = primaryChildFragment; + primaryChildFragment.childExpirationTime = NoWork; // Skip the primary children, and continue working on the // fallback children. next = fallbackChildFragment; @@ -1134,6 +1135,7 @@ function updateSuspenseComponent( )); fallbackChildFragment.effectTag |= Placement; child = primaryChildFragment; + primaryChildFragment.childExpirationTime = NoWork; // Skip the primary children, and continue working on the // fallback children. next = fallbackChildFragment; @@ -1435,6 +1437,7 @@ function beginWork( const nextState = workInProgress.memoizedState; const nextDidTimeout = nextState !== null && nextState.didTimeout; if (nextDidTimeout) { + child.childExpirationTime = NoWork; return child.sibling; } else { return child; diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js index 7386db830dcad..47dbb30d97f64 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js @@ -435,5 +435,53 @@ describe('ReactSuspense', () => { ]); expect(root).toMatchRenderedOutput('AB:2C'); }); + + it('bails out on timed-out primary children even if they receive an update', () => { + let instance; + class Stateful extends React.Component { + state = {step: 1}; + render() { + instance = this; + return ; + } + } + + function App(props) { + return ( + }> + + + + ); + } + + const root = ReactTestRenderer.create(); + + expect(ReactTestRenderer).toHaveYielded([ + 'Stateful', + 'Suspend! [A]', + 'Loading...', + ]); + + jest.advanceTimersByTime(1000); + expect(ReactTestRenderer).toHaveYielded(['Promise resolved [A]', 'A']); + expect(root).toMatchRenderedOutput('StatefulA'); + + root.update(); + expect(ReactTestRenderer).toHaveYielded([ + 'Stateful', + 'Suspend! [B]', + 'Loading...', + ]); + + instance.setState({step: 2}); + + jest.advanceTimersByTime(1000); + expect(ReactTestRenderer).toHaveYielded([ + 'Promise resolved [B]', + 'Stateful', + 'B', + ]); + }); }); });