From 578f3f0c9636f20a4fe42344d5a0ed2f059e85ef Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 27 Mar 2019 23:03:47 -0700 Subject: [PATCH] Allow DevTools to toggle Suspense state --- .../ReactDevToolsHooksIntegration-test.js | 49 +++++++++++++++++++ .../src/ReactFiberBeginWork.js | 7 +++ .../src/ReactFiberDevToolsHook.js | 14 ++++++ .../src/ReactFiberReconciler.js | 6 ++- 4 files changed, 75 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..e908f14279a23 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,43 @@ describe('React hooks DevTools integration', () => { }); } }); + + it('should support triggering suspense in DEV', () => { + let setCountFn; + + 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 3c5e0fddae0e1..ac8ad01395b35 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, @@ -1389,6 +1390,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 01e303c74bc42..6eebad85ad47e 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -404,7 +404,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; }