From d26d182cde9c9625795fbfb3f7e51f97f66cdabe Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 27 Mar 2019 23:03:47 -0700 Subject: [PATCH 1/6] Allow DevTools to toggle Suspense state --- .../ReactDevToolsHooksIntegration-test.js | 47 +++++++++++++++++++ .../src/ReactFiberBeginWork.js | 7 +++ .../src/ReactFiberDevToolsHook.js | 14 ++++++ .../src/ReactFiberReconciler.js | 6 ++- .../__tests__/ReactProfiler-test.internal.js | 1 + 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js index 113524f832af5..2b8087274dde6 100644 --- a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js @@ -16,15 +16,25 @@ describe('React hooks DevTools integration', () => { let ReactTestRenderer; let act; let overrideHookState; + let overrideProps; + let suspendedFibers; beforeEach(() => { + suspendedFibers = new Set(); global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { inject: injected => { overrideHookState = injected.overrideHookState; + overrideProps = injected.overrideProps; }, supportsFiber: true, onCommitFiberRoot: () => {}, onCommitFiberUnmount: () => {}, + shouldSuspendFiber(rendererId, fiber) { + return ( + suspendedFibers.has(fiber) || + (fiber.alternate && suspendedFibers.has(fiber.alternate)) + ); + }, }; jest.resetModules(); @@ -173,4 +183,41 @@ describe('React hooks DevTools integration', () => { }); } }); + + it('should support triggering suspense in DEV', () => { + function MyComponent() { + return 'Done'; + } + + const renderer = ReactTestRenderer.create( + + + , + ); + expect(renderer.toJSON()).toEqual('Done'); + + const fiber = renderer.root._currentFiber().return; + if (__DEV__) { + // Mark as loading + suspendedFibers.add(fiber); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON()).toEqual('Loading'); + + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON()).toEqual('Loading'); + + // Mark as done + suspendedFibers.delete(fiber); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON()).toEqual('Done'); + + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON()).toEqual('Done'); + + // Mark as loading again + suspendedFibers.add(fiber); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON()).toEqual('Loading'); + } + }); }); diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index e6b04911a7025..01b60efffe3a4 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -77,6 +77,7 @@ import { cloneChildFibers, } from './ReactChildFiber'; import {processUpdateQueue} from './ReactUpdateQueue'; +import {shouldSuspend} from './ReactFiberDevToolsHook'; import { NoWork, Never, @@ -1392,6 +1393,12 @@ function updateSuspenseComponent( const mode = workInProgress.mode; const nextProps = workInProgress.pendingProps; + if (__DEV__) { + if (shouldSuspend(workInProgress)) { + workInProgress.effectTag |= DidCapture; + } + } + // We should attempt to render the primary children unless this boundary // already suspended during this render (`alreadyCaptured` is true). let nextState: SuspenseState | null = workInProgress.memoizedState; diff --git a/packages/react-reconciler/src/ReactFiberDevToolsHook.js b/packages/react-reconciler/src/ReactFiberDevToolsHook.js index f7bebce841d5b..ba8432cb269bd 100644 --- a/packages/react-reconciler/src/ReactFiberDevToolsHook.js +++ b/packages/react-reconciler/src/ReactFiberDevToolsHook.js @@ -16,6 +16,9 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void; let onCommitFiberRoot = null; let onCommitFiberUnmount = null; +let shouldSuspendFiber = function() { + return false; +}; let hasLoggedError = false; function catchErrors(fn) { @@ -71,6 +74,13 @@ export function injectInternals(internals: Object): boolean { onCommitFiberUnmount = catchErrors(fiber => hook.onCommitFiberUnmount(rendererID, fiber), ); + if (__DEV__) { + if (hook.shouldSuspendFiber) { + shouldSuspendFiber = catchErrors(fiber => + hook.shouldSuspendFiber(rendererID, fiber), + ); + } + } } catch (err) { // Catch all errors because it is unsafe to throw during initialization. if (__DEV__) { @@ -96,3 +106,7 @@ export function onCommitUnmount(fiber: Fiber) { onCommitFiberUnmount(fiber); } } + +export function shouldSuspend(fiber: Fiber) { + return shouldSuspendFiber(fiber); +} diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index b5e1182376523..a01df581faa58 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -403,7 +403,11 @@ if (__DEV__) { // Support DevTools props for function components, forwardRef, memo, host components, etc. overrideProps = (fiber: Fiber, path: Array, value: any) => { flushPassiveEffects(); - fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value); + if (path.length > 0) { + fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value); + } else { + fiber.pendingProps = {...fiber.pendingProps}; + } if (fiber.alternate) { fiber.alternate.pendingProps = fiber.pendingProps; } diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index 95c3b54770db4..624ad95b6067e 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -105,6 +105,7 @@ const mockDevToolsForTest = () => { onCommitRoot: () => {}, onCommitUnmount: () => {}, isDevToolsPresent: true, + shouldSuspend: () => false, })); }; From efebb6741e79ec99fc8eab9147f5b66777125d3a Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 3 Apr 2019 19:35:20 +0100 Subject: [PATCH 2/6] Change API to overrideSuspense This lets detect support for overriding Suspense from DevTools. --- .../ReactDevToolsHooksIntegration-test.js | 62 +++++++++++-------- .../src/ReactFiberBeginWork.js | 2 +- .../src/ReactFiberDevToolsHook.js | 14 ----- .../src/ReactFiberReconciler.js | 12 ++++ 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js index 2b8087274dde6..0d8e32ba06c72 100644 --- a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js @@ -17,24 +17,18 @@ describe('React hooks DevTools integration', () => { let act; let overrideHookState; let overrideProps; - let suspendedFibers; + let overrideSuspense; beforeEach(() => { - suspendedFibers = new Set(); global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { inject: injected => { overrideHookState = injected.overrideHookState; overrideProps = injected.overrideProps; + overrideSuspense = injected.overrideSuspense; }, supportsFiber: true, onCommitFiberRoot: () => {}, onCommitFiberUnmount: () => {}, - shouldSuspendFiber(rendererId, fiber) { - return ( - suspendedFibers.has(fiber) || - (fiber.alternate && suspendedFibers.has(fiber.alternate)) - ); - }, }; jest.resetModules(); @@ -184,40 +178,56 @@ describe('React hooks DevTools integration', () => { } }); - it('should support triggering suspense in DEV', () => { + it('should support overriding suspense', () => { + if (__DEV__) { + // Lock the first render + overrideSuspense(() => true); + } + function MyComponent() { return 'Done'; } const renderer = ReactTestRenderer.create( - - - , +
+ + + +
, ); - expect(renderer.toJSON()).toEqual('Done'); - - const fiber = renderer.root._currentFiber().return; + const fiber = renderer.root._currentFiber().child; if (__DEV__) { - // Mark as loading - suspendedFibers.add(fiber); + // First render was locked + expect(renderer.toJSON().children).toEqual(['Loading']); overrideProps(fiber, [], null); // Re-render - expect(renderer.toJSON()).toEqual('Loading'); + expect(renderer.toJSON().children).toEqual(['Loading']); + // Release the lock + overrideSuspense(() => false); overrideProps(fiber, [], null); // Re-render - expect(renderer.toJSON()).toEqual('Loading'); + expect(renderer.toJSON().children).toEqual(['Done']); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON().children).toEqual(['Done']); - // Mark as done - suspendedFibers.delete(fiber); + // Lock again + overrideSuspense(() => true); overrideProps(fiber, [], null); // Re-render - expect(renderer.toJSON()).toEqual('Done'); + expect(renderer.toJSON().children).toEqual(['Loading']); + // Release the lock again + overrideSuspense(() => false); overrideProps(fiber, [], null); // Re-render - expect(renderer.toJSON()).toEqual('Done'); + expect(renderer.toJSON().children).toEqual(['Done']); - // Mark as loading again - suspendedFibers.add(fiber); + // Ensure it checks specific fibers. + overrideSuspense(f => f === fiber || f === fiber.alternate); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON().children).toEqual(['Loading']); + overrideSuspense(f => f !== fiber && f !== fiber.alternate); overrideProps(fiber, [], null); // Re-render - expect(renderer.toJSON()).toEqual('Loading'); + expect(renderer.toJSON().children).toEqual(['Done']); + } else { + expect(renderer.toJSON().children).toEqual(['Done']); } }); }); diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 01b60efffe3a4..2832021950f69 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -77,7 +77,6 @@ import { cloneChildFibers, } from './ReactChildFiber'; import {processUpdateQueue} from './ReactUpdateQueue'; -import {shouldSuspend} from './ReactFiberDevToolsHook'; import { NoWork, Never, @@ -97,6 +96,7 @@ import { registerSuspenseInstanceRetry, } from './ReactFiberHostConfig'; import type {SuspenseInstance} from './ReactFiberHostConfig'; +import {shouldSuspend} from './ReactFiberReconciler'; import { pushHostContext, pushHostContainer, diff --git a/packages/react-reconciler/src/ReactFiberDevToolsHook.js b/packages/react-reconciler/src/ReactFiberDevToolsHook.js index ba8432cb269bd..f7bebce841d5b 100644 --- a/packages/react-reconciler/src/ReactFiberDevToolsHook.js +++ b/packages/react-reconciler/src/ReactFiberDevToolsHook.js @@ -16,9 +16,6 @@ declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: Object | void; let onCommitFiberRoot = null; let onCommitFiberUnmount = null; -let shouldSuspendFiber = function() { - return false; -}; let hasLoggedError = false; function catchErrors(fn) { @@ -74,13 +71,6 @@ export function injectInternals(internals: Object): boolean { onCommitFiberUnmount = catchErrors(fiber => hook.onCommitFiberUnmount(rendererID, fiber), ); - if (__DEV__) { - if (hook.shouldSuspendFiber) { - shouldSuspendFiber = catchErrors(fiber => - hook.shouldSuspendFiber(rendererID, fiber), - ); - } - } } catch (err) { // Catch all errors because it is unsafe to throw during initialization. if (__DEV__) { @@ -106,7 +96,3 @@ export function onCommitUnmount(fiber: Fiber) { onCommitFiberUnmount(fiber); } } - -export function shouldSuspend(fiber: Fiber) { - return shouldSuspendFiber(fiber); -} diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index a01df581faa58..b985392e81c03 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -340,8 +340,15 @@ export function findHostInstanceWithNoPortals( return hostFiber.stateNode; } +let shouldSuspendImpl = fiber => false; + +export function shouldSuspend(fiber: Fiber): boolean { + return shouldSuspendImpl(fiber); +} + let overrideHookState = null; let overrideProps = null; +let overrideSuspense = null; if (__DEV__) { const copyWithSetImpl = ( @@ -413,6 +420,10 @@ if (__DEV__) { } scheduleWork(fiber, Sync); }; + + overrideSuspense = (newShouldSuspendImpl: Fiber => boolean) => { + shouldSuspendImpl = newShouldSuspendImpl; + }; } export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean { @@ -423,6 +434,7 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean { ...devToolsConfig, overrideHookState, overrideProps, + overrideSuspense, currentDispatcherRef: ReactCurrentDispatcher, findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null { const hostFiber = findCurrentHostFiber(fiber); From 90c91cb30f0a4d8c25ddf9899b030e70fe645ef7 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 3 Apr 2019 19:47:55 +0100 Subject: [PATCH 3/6] Add ConcurrentMode test --- .../ReactDevToolsHooksIntegration-test.js | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js index 0d8e32ba06c72..7a0577436ae40 100644 --- a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js @@ -14,6 +14,7 @@ describe('React hooks DevTools integration', () => { let React; let ReactDebugTools; let ReactTestRenderer; + let Scheduler; let act; let overrideHookState; let overrideProps; @@ -36,6 +37,7 @@ describe('React hooks DevTools integration', () => { React = require('react'); ReactDebugTools = require('react-debug-tools'); ReactTestRenderer = require('react-test-renderer'); + Scheduler = require('scheduler'); act = ReactTestRenderer.act; }); @@ -178,7 +180,7 @@ describe('React hooks DevTools integration', () => { } }); - it('should support overriding suspense', () => { + it('should support overriding suspense in sync mode', () => { if (__DEV__) { // Lock the first render overrideSuspense(() => true); @@ -230,4 +232,59 @@ describe('React hooks DevTools integration', () => { expect(renderer.toJSON().children).toEqual(['Done']); } }); + + it('should support overriding suspense in concurrent mode', () => { + if (__DEV__) { + // Lock the first render + overrideSuspense(() => true); + } + + function MyComponent() { + return 'Done'; + } + + const renderer = ReactTestRenderer.create( +
+ + + +
, + {unstable_isConcurrent: true}, + ); + expect(Scheduler).toFlushAndYield([]); + const fiber = renderer.root._currentFiber().child; + if (__DEV__) { + // First render was locked + expect(renderer.toJSON().children).toEqual(['Loading']); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON().children).toEqual(['Loading']); + // Release the lock + overrideSuspense(() => false); + + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON().children).toEqual(['Done']); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON().children).toEqual(['Done']); + + // Lock again + overrideSuspense(() => true); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON().children).toEqual(['Loading']); + + // Release the lock again + overrideSuspense(() => false); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON().children).toEqual(['Done']); + + // Ensure it checks specific fibers. + overrideSuspense(f => f === fiber || f === fiber.alternate); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON().children).toEqual(['Loading']); + overrideSuspense(f => f !== fiber && f !== fiber.alternate); + overrideProps(fiber, [], null); // Re-render + expect(renderer.toJSON().children).toEqual(['Done']); + } else { + expect(renderer.toJSON().children).toEqual(['Done']); + } + }); }); From 0898307d1fde9a366bc8420cc4d33573fd530d77 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 3 Apr 2019 19:49:47 +0100 Subject: [PATCH 4/6] Newlines --- .../src/__tests__/ReactDevToolsHooksIntegration-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js index 7a0577436ae40..d7bbdf1bd8baa 100644 --- a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js @@ -203,9 +203,9 @@ describe('React hooks DevTools integration', () => { expect(renderer.toJSON().children).toEqual(['Loading']); overrideProps(fiber, [], null); // Re-render expect(renderer.toJSON().children).toEqual(['Loading']); + // Release the lock overrideSuspense(() => false); - overrideProps(fiber, [], null); // Re-render expect(renderer.toJSON().children).toEqual(['Done']); overrideProps(fiber, [], null); // Re-render @@ -258,9 +258,9 @@ describe('React hooks DevTools integration', () => { expect(renderer.toJSON().children).toEqual(['Loading']); overrideProps(fiber, [], null); // Re-render expect(renderer.toJSON().children).toEqual(['Loading']); + // Release the lock overrideSuspense(() => false); - overrideProps(fiber, [], null); // Re-render expect(renderer.toJSON().children).toEqual(['Done']); overrideProps(fiber, [], null); // Re-render From dc3010cbf3081f448ed20c38df9b23b7153a3bf3 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 3 Apr 2019 19:51:22 +0100 Subject: [PATCH 5/6] Remove unnecessary change --- packages/react/src/__tests__/ReactProfiler-test.internal.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index 624ad95b6067e..95c3b54770db4 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -105,7 +105,6 @@ const mockDevToolsForTest = () => { onCommitRoot: () => {}, onCommitUnmount: () => {}, isDevToolsPresent: true, - shouldSuspend: () => false, })); }; From 35cf972e30dfe3f3cb561b4f309d5da489abf3e8 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 4 Apr 2019 09:27:08 +0100 Subject: [PATCH 6/6] Naming changes --- .../ReactDevToolsHooksIntegration-test.js | 60 +++++++++---------- .../src/ReactFiberReconciler.js | 19 +++--- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js index d7bbdf1bd8baa..f7ea7ffebc031 100644 --- a/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js @@ -17,15 +17,15 @@ describe('React hooks DevTools integration', () => { let Scheduler; let act; let overrideHookState; - let overrideProps; - let overrideSuspense; + let scheduleUpdate; + let setSuspenseHandler; beforeEach(() => { global.__REACT_DEVTOOLS_GLOBAL_HOOK__ = { inject: injected => { overrideHookState = injected.overrideHookState; - overrideProps = injected.overrideProps; - overrideSuspense = injected.overrideSuspense; + scheduleUpdate = injected.scheduleUpdate; + setSuspenseHandler = injected.setSuspenseHandler; }, supportsFiber: true, onCommitFiberRoot: () => {}, @@ -183,7 +183,7 @@ describe('React hooks DevTools integration', () => { it('should support overriding suspense in sync mode', () => { if (__DEV__) { // Lock the first render - overrideSuspense(() => true); + setSuspenseHandler(() => true); } function MyComponent() { @@ -201,32 +201,32 @@ describe('React hooks DevTools integration', () => { if (__DEV__) { // First render was locked expect(renderer.toJSON().children).toEqual(['Loading']); - overrideProps(fiber, [], null); // Re-render + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Loading']); // Release the lock - overrideSuspense(() => false); - overrideProps(fiber, [], null); // Re-render + setSuspenseHandler(() => false); + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Done']); - overrideProps(fiber, [], null); // Re-render + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Done']); // Lock again - overrideSuspense(() => true); - overrideProps(fiber, [], null); // Re-render + setSuspenseHandler(() => true); + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Loading']); // Release the lock again - overrideSuspense(() => false); - overrideProps(fiber, [], null); // Re-render + setSuspenseHandler(() => false); + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Done']); // Ensure it checks specific fibers. - overrideSuspense(f => f === fiber || f === fiber.alternate); - overrideProps(fiber, [], null); // Re-render + setSuspenseHandler(f => f === fiber || f === fiber.alternate); + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Loading']); - overrideSuspense(f => f !== fiber && f !== fiber.alternate); - overrideProps(fiber, [], null); // Re-render + setSuspenseHandler(f => f !== fiber && f !== fiber.alternate); + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Done']); } else { expect(renderer.toJSON().children).toEqual(['Done']); @@ -236,7 +236,7 @@ describe('React hooks DevTools integration', () => { it('should support overriding suspense in concurrent mode', () => { if (__DEV__) { // Lock the first render - overrideSuspense(() => true); + setSuspenseHandler(() => true); } function MyComponent() { @@ -256,32 +256,32 @@ describe('React hooks DevTools integration', () => { if (__DEV__) { // First render was locked expect(renderer.toJSON().children).toEqual(['Loading']); - overrideProps(fiber, [], null); // Re-render + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Loading']); // Release the lock - overrideSuspense(() => false); - overrideProps(fiber, [], null); // Re-render + setSuspenseHandler(() => false); + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Done']); - overrideProps(fiber, [], null); // Re-render + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Done']); // Lock again - overrideSuspense(() => true); - overrideProps(fiber, [], null); // Re-render + setSuspenseHandler(() => true); + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Loading']); // Release the lock again - overrideSuspense(() => false); - overrideProps(fiber, [], null); // Re-render + setSuspenseHandler(() => false); + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Done']); // Ensure it checks specific fibers. - overrideSuspense(f => f === fiber || f === fiber.alternate); - overrideProps(fiber, [], null); // Re-render + setSuspenseHandler(f => f === fiber || f === fiber.alternate); + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Loading']); - overrideSuspense(f => f !== fiber && f !== fiber.alternate); - overrideProps(fiber, [], null); // Re-render + setSuspenseHandler(f => f !== fiber && f !== fiber.alternate); + scheduleUpdate(fiber); // Re-render expect(renderer.toJSON().children).toEqual(['Done']); } else { expect(renderer.toJSON().children).toEqual(['Done']); diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index b985392e81c03..77fadd31e9b2e 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -348,7 +348,8 @@ export function shouldSuspend(fiber: Fiber): boolean { let overrideHookState = null; let overrideProps = null; -let overrideSuspense = null; +let scheduleUpdate = null; +let setSuspenseHandler = null; if (__DEV__) { const copyWithSetImpl = ( @@ -410,18 +411,19 @@ if (__DEV__) { // Support DevTools props for function components, forwardRef, memo, host components, etc. overrideProps = (fiber: Fiber, path: Array, value: any) => { flushPassiveEffects(); - if (path.length > 0) { - fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value); - } else { - fiber.pendingProps = {...fiber.pendingProps}; - } + fiber.pendingProps = copyWithSet(fiber.memoizedProps, path, value); if (fiber.alternate) { fiber.alternate.pendingProps = fiber.pendingProps; } scheduleWork(fiber, Sync); }; - overrideSuspense = (newShouldSuspendImpl: Fiber => boolean) => { + scheduleUpdate = (fiber: Fiber) => { + flushPassiveEffects(); + scheduleWork(fiber, Sync); + }; + + setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => { shouldSuspendImpl = newShouldSuspendImpl; }; } @@ -434,7 +436,8 @@ export function injectIntoDevTools(devToolsConfig: DevToolsConfig): boolean { ...devToolsConfig, overrideHookState, overrideProps, - overrideSuspense, + setSuspenseHandler, + scheduleUpdate, currentDispatcherRef: ReactCurrentDispatcher, findHostInstanceByFiber(fiber: Fiber): Instance | TextInstance | null { const hostFiber = findCurrentHostFiber(fiber);