Repository: facebook/react
Pull request: [DevTools] Only show Suspense rects matching "unique-suspenders-only" filter

Basically make the UI consistent between timeline and rects

https://github.com/user-attachments/assets/5ef5545d-89e2-4b22-ac49-5442b7ca6e81



Changed files:
filename: packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.css
patch: @@ -19,6 +19,10 @@
    overflow: hidden;
 }
 
+.SuspenseRectsScaledRect[data-visible='false'] > .SuspenseRectsBoundaryChildren {
+  overflow: initial;
+}
+
 .SuspenseRectsRect {
   box-shadow: var(--elevation-4);
   pointer-events: all;
@@ -31,6 +35,11 @@
   outline-color: var(--color-background-selected);
 }
 
+.SuspenseRectsScaledRect[data-visible='false'] {
+  pointer-events: none;
+  outline-width: 0;
+}
+
 /* highlight this boundary */
 .SuspenseRectsBoundary:hover:not(:has(.SuspenseRectsBoundary:hover)) > .SuspenseRectsRect, .SuspenseRectsBoundary[data-highlighted='true'] > .SuspenseRectsRect {
     background-color: var(--color-background-hover);

filename: packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js
patch: @@ -34,10 +34,12 @@ import {
 function ScaledRect({
   className,
   rect,
+  visible,
   ...props
 }: {
   className: string,
   rect: Rect,
+  visible: boolean,
   ...
 }): React$Node {
   const viewBox = useContext(ViewBox);
@@ -50,6 +52,7 @@ function ScaledRect({
     <div
       {...props}
       className={styles.SuspenseRectsScaledRect + ' ' + className}
+      data-visible={visible}
       style={{
         width,
         height,
@@ -68,6 +71,7 @@ function SuspenseRects({
   const store = useContext(StoreContext);
   const treeDispatch = useContext(TreeDispatcherContext);
   const suspenseTreeDispatch = useContext(SuspenseTreeDispatcherContext);
+  const {uniqueSuspendersOnly} = useContext(SuspenseTreeStateContext);
 
   const {inspectedElementID} = useContext(TreeStateContext);
 
@@ -79,6 +83,7 @@ function SuspenseRects({
     // getSuspenseByID will have already warned
     return null;
   }
+  const visible = suspense.hasUniqueSuspenders || !uniqueSuspendersOnly;
 
   function handleClick(event: SyntheticMouseEvent) {
     if (event.defaultPrevented) {
@@ -117,9 +122,13 @@ function SuspenseRects({
   const boundingBox = getBoundingBox(suspense.rects);
 
   return (
-    <ScaledRect rect={boundingBox} className={styles.SuspenseRectsBoundary}>
+    <ScaledRect
+      rect={boundingBox}
+      className={styles.SuspenseRectsBoundary}
+      visible={visible}>
       <ViewBox.Provider value={boundingBox}>
-        {suspense.rects !== null &&
+        {visible &&
+          suspense.rects !== null &&
           suspense.rects.map((rect, index) => {
             return (
               <ScaledRect
@@ -245,6 +254,7 @@ function SuspenseRectsContainer(): React$Node {
   // TODO: This relies on a full re-render of all children when the Suspense tree changes.
   const {roots} = useContext(SuspenseTreeStateContext);
 
+  // TODO: bbox does not consider uniqueSuspendersOnly filter
   const boundingBox = getDocumentBoundingRect(store, roots);
 
   const boundingBoxWidth = boundingBox.width;

filename: packages/react-devtools-shell/src/app/SuspenseTree/index.js
patch: @@ -184,8 +184,12 @@ function EmptySuspense() {
 // $FlowFixMe[missing-local-annot]
 function PrimaryFallbackTest({initialSuspend}) {
   const [suspend, setSuspend] = useState(initialSuspend);
-  const fallbackStep = useTestSequence('fallback', Fallback1, Fallback2);
-  const primaryStep = useTestSequence('primary', Primary1, Primary2);
+  const [fallbackStepIndex, fallbackStep] = useTestSequence(
+    'fallback',
+    Fallback1,
+    Fallback2,
+  );
+  const [, primaryStep] = useTestSequence('primary', Primary1, Primary2);
   return (
     <Fragment>
       <label>
@@ -198,7 +202,11 @@ function PrimaryFallbackTest({initialSuspend}) {
       </label>
       <br />
       <Suspense fallback={fallbackStep}>
-        {suspend ? <Never /> : primaryStep}
+        {suspend ? (
+          <Never id={`primary-fallback-test-${fallbackStepIndex}`} />
+        ) : (
+          primaryStep
+        )}
       </Suspense>
     </Fragment>
   );
@@ -227,7 +235,7 @@ function useTestSequence(label: string, T1: any => any, T2: any => any) {
       {next} <T2 prop={step}>goodbye</T2>
     </Fragment>,
   ];
-  return allSteps[step];
+  return [step, allSteps[step]];
 }
 
 function NestedSuspenseTest() {
@@ -252,7 +260,7 @@ function Parent() {
       </Suspense>
       <br />
       <Suspense fallback={<Fallback1>This will never load</Fallback1>}>
-        <Never />
+        <Never id="parent-never" />
       </Suspense>
       <br />
       <b>
@@ -298,14 +306,48 @@ function LoadLater() {
           Loaded! Click to suspend again.
         </Primary1>
       ) : (
-        <Never />
+        <Never id="load-later" />
       )}
     </Suspense>
   );
 }
 
-function Never() {
-  throw new Promise(resolve => {});
+function readRecord(promise: any): any {
+  if (typeof React.use === 'function') {
+    return React.use(promise);
+  }
+  switch (promise.status) {
+    case 'pending':
+      throw promise;
+    case 'rejected':
+      throw promise.reason;
+    case 'fulfilled':
+      return promise.value;
+    default:
+      promise.status = 'pending';
+      promise.then(
+        value => {
+          promise.status = 'fulfilled';
+          promise.value = value;
+        },
+        reason => {
+          promise.status = 'rejected';
+          promise.reason = reason;
+        },
+      );
+      throw promise;
+  }
+}
+
+const nevers = new Map<string, Promise<empty>>();
+function Never({id}: {id: string}) {
+  let promise = nevers.get(id);
+  if (!promise) {
+    promise = new Promise(() => {});
+    (promise as any).displayName = id;
+    nevers.set(id, promise);
+  }
+  readRecord(promise);
 }
 
 function Fallback1({prop, ...rest}: any) {