Skip to content

Commit be11cb5

Browse files
authored
[DevTools] Tweak the presentation of the Promise value (facebook#34097)
Show the value as "fulfilled: Type" or "rejected: Type" immediately instead of having to expand it twice. We could show all the properties of the object immediately like we do in the Performance Track but it's not always particularly interesting data in the value that isn't already in the header. I also moved it to the end after the stack traces since I think the stack is more interesting but I'm also visually trying to connect the stack trace with the "name" since typically the "name" will come from part of the stack trace. Before: <img width="517" height="433" alt="Screenshot 2025-08-03 at 11 39 49 PM" src="https://github.com/user-attachments/assets/ad28d8a2-c149-4957-a393-20ff3932a819" /> After: <img width="520" height="476" alt="Screenshot 2025-08-03 at 11 58 35 PM" src="https://github.com/user-attachments/assets/53a755b0-bb68-4305-9d16-d6fac7ca4910" />
1 parent 557745e commit be11cb5

File tree

4 files changed

+51
-27
lines changed

4 files changed

+51
-27
lines changed

packages/react-client/src/ReactFlightPerformanceTrack.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ export function logComponentAwait(
490490
if (typeof value === 'object' && value !== null) {
491491
addObjectToProperties(value, properties, 0, '');
492492
} else if (value !== undefined) {
493-
addValueToProperties('Resolved', value, properties, 0, '');
493+
addValueToProperties('awaited value', value, properties, 0, '');
494494
}
495495
const tooltipText = getIOLongName(
496496
asyncInfo.awaited,
@@ -547,7 +547,7 @@ export function logIOInfoErrored(
547547
String(error.message)
548548
: // eslint-disable-next-line react-internal/safe-string-coercion
549549
String(error);
550-
const properties = [['Rejected', message]];
550+
const properties = [['rejected with', message]];
551551
const tooltipText =
552552
getIOLongName(ioInfo, description, ioInfo.env, rootEnv) + ' Rejected';
553553
debugTask.run(

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSharedStyles.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,11 @@
9797
}
9898

9999
.CollapsableContent {
100-
padding: 0.25rem 0;
100+
margin-top: -0.25rem;
101101
}
102102

103103
.PreviewContainer {
104-
padding: 0 0.25rem 0.25rem 0.25rem;
104+
padding: 0.25rem;
105105
}
106106

107107
.TimeBarContainer {

packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,10 @@ function SuspendedByRow({
107107
}
108108

109109
const value: any = asyncInfo.awaited.value;
110-
const isErrored =
111-
value !== null &&
112-
typeof value === 'object' &&
113-
value[meta.name] === 'rejected Thenable';
114-
110+
const metaName =
111+
value !== null && typeof value === 'object' ? value[meta.name] : null;
112+
const isFulfilled = metaName === 'fulfilled Thenable';
113+
const isRejected = metaName === 'rejected Thenable';
115114
return (
116115
<div className={styles.CollapsableRow}>
117116
<Button
@@ -136,7 +135,7 @@ function SuspendedByRow({
136135
<div className={styles.TimeBarContainer}>
137136
<div
138137
className={
139-
!isErrored ? styles.TimeBarSpan : styles.TimeBarSpanErrored
138+
!isRejected ? styles.TimeBarSpan : styles.TimeBarSpanErrored
140139
}
141140
style={{
142141
left: left.toFixed(2) + '%',
@@ -147,6 +146,20 @@ function SuspendedByRow({
147146
</Button>
148147
{isOpen && (
149148
<div className={styles.CollapsableContent}>
149+
{stack !== null && stack.length > 0 && (
150+
<StackTraceView stack={stack} />
151+
)}
152+
{owner !== null && owner.id !== inspectedElement.id ? (
153+
<OwnerView
154+
key={owner.id}
155+
displayName={owner.displayName || 'Anonymous'}
156+
hocDisplayNames={owner.hocDisplayNames}
157+
compiledWithForget={owner.compiledWithForget}
158+
id={owner.id}
159+
isInStore={store.containsElement(owner.id)}
160+
type={owner.type}
161+
/>
162+
) : null}
150163
<div className={styles.PreviewContainer}>
151164
<KeyValue
152165
alphaSort={true}
@@ -158,27 +171,27 @@ function SuspendedByRow({
158171
element={element}
159172
hidden={false}
160173
inspectedElement={inspectedElement}
161-
name={'Promise'}
162-
path={[index, 'awaited', 'value']}
174+
name={
175+
isFulfilled
176+
? 'awaited value'
177+
: isRejected
178+
? 'rejected with'
179+
: 'pending value'
180+
}
181+
path={
182+
isFulfilled
183+
? [index, 'awaited', 'value', 'value']
184+
: isRejected
185+
? [index, 'awaited', 'value', 'reason']
186+
: [index, 'awaited', 'value']
187+
}
163188
pathRoot="suspendedBy"
164189
store={store}
165-
value={asyncInfo.awaited.value}
190+
value={
191+
isFulfilled ? value.value : isRejected ? value.reason : value
192+
}
166193
/>
167194
</div>
168-
{stack !== null && stack.length > 0 && (
169-
<StackTraceView stack={stack} />
170-
)}
171-
{owner !== null && owner.id !== inspectedElement.id ? (
172-
<OwnerView
173-
key={owner.id}
174-
displayName={owner.displayName || 'Anonymous'}
175-
hocDisplayNames={owner.hocDisplayNames}
176-
compiledWithForget={owner.compiledWithForget}
177-
id={owner.id}
178-
isInStore={store.containsElement(owner.id)}
179-
type={owner.type}
180-
/>
181-
) : null}
182195
</div>
183196
)}
184197
</div>

packages/react-devtools-shared/src/hydration.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import type {
2121
InspectedElementPath,
2222
} from 'react-devtools-shared/src/frontend/types';
2323

24+
import noop from 'shared/noop';
25+
2426
export const meta = {
2527
inspectable: (Symbol('inspectable'): symbol),
2628
inspected: (Symbol('inspected'): symbol),
@@ -317,6 +319,15 @@ export function dehydrate(
317319
};
318320
}
319321

322+
if (
323+
data.status === 'resolved_model' ||
324+
data.status === 'resolve_module'
325+
) {
326+
// This looks it's a lazy initialization pattern such in Flight.
327+
// Since we're about to inspect it. Let's eagerly initialize it.
328+
data.then(noop);
329+
}
330+
320331
switch (data.status) {
321332
case 'fulfilled': {
322333
const unserializableValue: Unserializable = {

0 commit comments

Comments
 (0)