From 3cc564547c81b6e9a3bd6a04aa0725e668ca6559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Fri, 18 Oct 2019 17:18:36 -0700 Subject: [PATCH] SuspenseList support in DevTools (#17145) * SuspenseList support in DevTools This adds SuspenseList tags to DevTools so that the name properly shows up. It also switches to use the tag instead of Symbol type for Suspense components. We shouldn't rely on the type for any built-ins since that field will disappear from the fibers. How the Fibers get created is an implementation detail that can change e.g. with a compiler or if we use instanceof checks that are faster than symbol comparisons. * Add SuspenseList test to shell app --- .../__snapshots__/store-test.js.snap | 19 ++++++++++ .../src/__tests__/store-test.js | 33 +++++++++++++++++ .../src/backend/renderer.js | 36 +++++++++---------- packages/react-devtools-shared/src/types.js | 3 +- .../src/app/SuspenseTree/index.js | 26 +++++++++++++- 5 files changed, 95 insertions(+), 22 deletions(-) diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/store-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/store-test.js.snap index 39bddd9b583ab..bbd434be6fd21 100644 --- a/packages/react-devtools-shared/src/__tests__/__snapshots__/store-test.js.snap +++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/store-test.js.snap @@ -16,6 +16,25 @@ exports[`Store collapseNodesByDefault:false should display Suspense nodes proper `; +exports[`Store collapseNodesByDefault:false should display a partially rendered SuspenseList: 1: loading 1`] = ` +[root] + ▾ + ▾ + + ▾ + +`; + +exports[`Store collapseNodesByDefault:false should display a partially rendered SuspenseList: 2: resolved 1`] = ` +[root] + ▾ + ▾ + + ▾ + + +`; + exports[`Store collapseNodesByDefault:false should filter DOM nodes from the store tree: 1: mount 1`] = ` [root] ▾ diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js index 06f9c69ae14e0..bc8885a2a582f 100644 --- a/packages/react-devtools-shared/src/__tests__/store-test.js +++ b/packages/react-devtools-shared/src/__tests__/store-test.js @@ -342,6 +342,39 @@ describe('Store', () => { expect(store).toMatchSnapshot('13: third child is suspended'); }); + it('should display a partially rendered SuspenseList', () => { + const Loading = () =>
Loading...
; + const SuspendingComponent = () => { + throw new Promise(() => {}); + }; + const Component = () => { + return
Hello
; + }; + const Wrapper = ({shouldSuspense}) => ( + + + + }> + {shouldSuspense ? : } + + + + + ); + + const container = document.createElement('div'); + const root = ReactDOM.createRoot(container); + act(() => { + root.render(); + }); + expect(store).toMatchSnapshot('1: loading'); + + act(() => { + root.render(); + }); + expect(store).toMatchSnapshot('2: resolved'); + }); + it('should support collapsing parts of the tree', () => { const Grandparent = ({count}) => ( diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 6f285495c07dd..be993282a946e 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -23,6 +23,7 @@ import { ElementTypeProfiler, ElementTypeRoot, ElementTypeSuspense, + ElementTypeSuspenseList, } from 'react-devtools-shared/src/types'; import { getDisplayName, @@ -91,9 +92,6 @@ type ReactSymbolsType = { PROFILER_SYMBOL_STRING: string, STRICT_MODE_NUMBER: number, STRICT_MODE_SYMBOL_STRING: string, - SUSPENSE_NUMBER: number, - SUSPENSE_SYMBOL_STRING: string, - DEPRECATED_PLACEHOLDER_SYMBOL_STRING: string, SCOPE_NUMBER: number, SCOPE_SYMBOL_STRING: string, }; @@ -129,6 +127,7 @@ type ReactTypeOfWorkType = {| Profiler: number, SimpleMemoComponent: number, SuspenseComponent: number, + SuspenseListComponent: number, YieldComponent: number, |}; @@ -170,9 +169,6 @@ export function getInternalReactConstants( PROFILER_SYMBOL_STRING: 'Symbol(react.profiler)', STRICT_MODE_NUMBER: 0xeacc, STRICT_MODE_SYMBOL_STRING: 'Symbol(react.strict_mode)', - SUSPENSE_NUMBER: 0xead1, - SUSPENSE_SYMBOL_STRING: 'Symbol(react.suspense)', - DEPRECATED_PLACEHOLDER_SYMBOL_STRING: 'Symbol(react.placeholder)', SCOPE_NUMBER: 0xead7, SCOPE_SYMBOL_STRING: 'Symbol(react.scope)', }; @@ -227,6 +223,7 @@ export function getInternalReactConstants( Profiler: 12, SimpleMemoComponent: 15, SuspenseComponent: 13, + SuspenseListComponent: 19, // Experimental YieldComponent: -1, // Removed }; } else if (gte(version, '16.4.3-alpha')) { @@ -252,6 +249,7 @@ export function getInternalReactConstants( Profiler: 15, SimpleMemoComponent: -1, // Doesn't exist yet SuspenseComponent: 16, + SuspenseListComponent: -1, // Doesn't exist yet YieldComponent: -1, // Removed }; } else { @@ -277,6 +275,7 @@ export function getInternalReactConstants( Profiler: 15, SimpleMemoComponent: -1, // Doesn't exist yet SuspenseComponent: 16, + SuspenseListComponent: -1, // Doesn't exist yet YieldComponent: 9, }; } @@ -307,6 +306,8 @@ export function getInternalReactConstants( Fragment, MemoComponent, SimpleMemoComponent, + SuspenseComponent, + SuspenseListComponent, } = ReactTypeOfWork; const { @@ -319,9 +320,6 @@ export function getInternalReactConstants( CONTEXT_CONSUMER_SYMBOL_STRING, STRICT_MODE_NUMBER, STRICT_MODE_SYMBOL_STRING, - SUSPENSE_NUMBER, - SUSPENSE_SYMBOL_STRING, - DEPRECATED_PLACEHOLDER_SYMBOL_STRING, PROFILER_NUMBER, PROFILER_SYMBOL_STRING, SCOPE_NUMBER, @@ -370,6 +368,10 @@ export function getInternalReactConstants( } else { return getDisplayName(type, 'Anonymous'); } + case SuspenseComponent: + return 'Suspense'; + case SuspenseListComponent: + return 'SuspenseList'; default: const typeSymbol = getTypeSymbol(type); @@ -398,10 +400,6 @@ export function getInternalReactConstants( case STRICT_MODE_NUMBER: case STRICT_MODE_SYMBOL_STRING: return null; - case SUSPENSE_NUMBER: - case SUSPENSE_SYMBOL_STRING: - case DEPRECATED_PLACEHOLDER_SYMBOL_STRING: - return 'Suspense'; case PROFILER_NUMBER: case PROFILER_SYMBOL_STRING: return `Profiler(${fiber.memoizedProps.id})`; @@ -457,6 +455,7 @@ export function attach( MemoComponent, SimpleMemoComponent, SuspenseComponent, + SuspenseListComponent, } = ReactTypeOfWork; const { ImmediatePriority, @@ -478,9 +477,6 @@ export function attach( PROFILER_SYMBOL_STRING, STRICT_MODE_NUMBER, STRICT_MODE_SYMBOL_STRING, - SUSPENSE_NUMBER, - SUSPENSE_SYMBOL_STRING, - DEPRECATED_PLACEHOLDER_SYMBOL_STRING, } = ReactSymbols; const { @@ -711,6 +707,10 @@ export function attach( case MemoComponent: case SimpleMemoComponent: return ElementTypeMemo; + case SuspenseComponent: + return ElementTypeSuspense; + case SuspenseListComponent: + return ElementTypeSuspenseList; default: const typeSymbol = getTypeSymbol(type); @@ -728,10 +728,6 @@ export function attach( case STRICT_MODE_NUMBER: case STRICT_MODE_SYMBOL_STRING: return ElementTypeOtherOrUnknown; - case SUSPENSE_NUMBER: - case SUSPENSE_SYMBOL_STRING: - case DEPRECATED_PLACEHOLDER_SYMBOL_STRING: - return ElementTypeSuspense; case PROFILER_NUMBER: case PROFILER_SYMBOL_STRING: return ElementTypeProfiler; diff --git a/packages/react-devtools-shared/src/types.js b/packages/react-devtools-shared/src/types.js index 16ac54989d19a..e2463a6aad26c 100644 --- a/packages/react-devtools-shared/src/types.js +++ b/packages/react-devtools-shared/src/types.js @@ -31,11 +31,12 @@ export const ElementTypeOtherOrUnknown = 9; export const ElementTypeProfiler = 10; export const ElementTypeRoot = 11; export const ElementTypeSuspense = 12; +export const ElementTypeSuspenseList = 13; // Different types of elements displayed in the Elements tree. // These types may be used to visually distinguish types, // or to enable/disable certain functionality. -export type ElementType = 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; +export type ElementType = 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13; // WARNING // The values below are referenced by ComponentFilters (which are saved via localStorage). diff --git a/packages/react-devtools-shell/src/app/SuspenseTree/index.js b/packages/react-devtools-shell/src/app/SuspenseTree/index.js index a19442de5c812..4763b51a42960 100644 --- a/packages/react-devtools-shell/src/app/SuspenseTree/index.js +++ b/packages/react-devtools-shell/src/app/SuspenseTree/index.js @@ -7,7 +7,7 @@ * @flow */ -import React, {Fragment, Suspense, useState} from 'react'; +import React, {Fragment, Suspense, SuspenseList, useState} from 'react'; function SuspenseTree() { return ( @@ -18,6 +18,7 @@ function SuspenseTree() {

Fallback to Primary Cycle

+ ); } @@ -102,6 +103,29 @@ function Parent() { ); } +function SuspenseListTest() { + return ( + <> +

SuspenseList

+ +
+ Loading 1}> + Hello + +
+
+ +
+
+ Loading 2}> + World + +
+
+ + ); +} + function LoadLater() { const [loadChild, setLoadChild] = useState(0); return (