@@ -133,15 +133,22 @@ import type {
133
133
import type { Source } from 'react-devtools-shared/src/shared/types' ;
134
134
import { getStackByFiberInDevAndProd } from './DevToolsFiberComponentStack' ;
135
135
136
+ // Kinds
136
137
const FIBER_INSTANCE = 0 ;
137
138
// const VIRTUAL_INSTANCE = 1;
138
139
140
+ // Flags
141
+ const FORCE_SUSPENSE_FALLBACK = /* */ 0b001 ;
142
+ const FORCE_ERROR = /* */ 0b010 ;
143
+ const FORCE_ERROR_RESET = /* */ 0b100 ;
144
+
139
145
// This type represents a stateful instance of a Client Component i.e. a Fiber pair.
140
146
// These instances also let us track stateful DevTools meta data like id and warnings.
141
147
type FiberInstance = {
142
148
kind : 0 ,
143
149
id : number ,
144
150
parent : null | DevToolsInstance , // virtual parent
151
+ flags : number , // Force Error/Suspense
145
152
componentStack : null | string ,
146
153
errors : null | Map < string , number> , // error messages and count
147
154
warnings : null | Map < string , number> , // warning messages and count
@@ -153,6 +160,7 @@ function createFiberInstance(fiber: Fiber): FiberInstance {
153
160
kind : 0 ,
154
161
id : getUID ( ) ,
155
162
parent : null ,
163
+ flags : 0 ,
156
164
componentStack : null ,
157
165
errors : null ,
158
166
warnings : null ,
@@ -169,6 +177,7 @@ type VirtualInstance = {
169
177
kind : 1 ,
170
178
id : number ,
171
179
parent : null | DevToolsInstance , // virtual parent
180
+ flags : number ,
172
181
componentStack : null | string ,
173
182
// Errors and Warnings happen per ReactComponentInfo which can appear in
174
183
// multiple places but we track them per stateful VirtualInstance so
@@ -893,9 +902,12 @@ export function attach(
893
902
args : $ReadOnlyArray < any > ,
894
903
) : void {
895
904
if ( type === 'error' ) {
896
- const maybeID = getFiberIDUnsafe ( fiber ) ;
905
+ let fiberInstance = fiberToFiberInstanceMap . get ( fiber ) ;
906
+ if ( fiberInstance === undefined && fiber . alternate !== null ) {
907
+ fiberInstance = fiberToFiberInstanceMap . get ( fiber . alternate ) ;
908
+ }
897
909
// if this is an error simulated by us to trigger error boundary, ignore
898
- if ( maybeID != null && forceErrorForFiberIDs . get ( maybeID ) === true ) {
910
+ if ( fiberInstance !== undefined && fiberInstance . flags & FORCE_ERROR ) {
899
911
return ;
900
912
}
901
913
}
@@ -1343,13 +1355,27 @@ export function attach(
1343
1355
}
1344
1356
1345
1357
untrackFibersSet.forEach(fiber => {
1346
- const fiberID = getFiberIDUnsafe ( fiber ) ;
1347
- if ( fiberID !== null ) {
1348
- idToDevToolsInstanceMap . delete ( fiberID ) ;
1358
+ const fiberInstance = fiberToFiberInstanceMap . get ( fiber ) ;
1359
+ if ( fiberInstance !== undefined ) {
1360
+ idToDevToolsInstanceMap . delete ( fiberInstance . id ) ;
1349
1361
1350
1362
// Also clear any errors/warnings associated with this fiber.
1351
- clearErrorsForElementID ( fiberID ) ;
1352
- clearWarningsForElementID ( fiberID ) ;
1363
+ clearErrorsForElementID ( fiberInstance . id ) ;
1364
+ clearWarningsForElementID ( fiberInstance . id ) ;
1365
+ if ( fiberInstance . flags & FORCE_ERROR ) {
1366
+ fiberInstance . flags &= ~ FORCE_ERROR ;
1367
+ forceErrorCount -- ;
1368
+ if ( forceErrorCount === 0 && setErrorHandler != null ) {
1369
+ setErrorHandler ( shouldErrorFiberAlwaysNull ) ;
1370
+ }
1371
+ }
1372
+ if (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK ) {
1373
+ fiberInstance . flags &= ~ FORCE_SUSPENSE_FALLBACK ;
1374
+ forceFallbackCount -- ;
1375
+ if ( forceFallbackCount === 0 && setSuspenseHandler != null ) {
1376
+ setSuspenseHandler ( shouldSuspendFiberAlwaysFalse ) ;
1377
+ }
1378
+ }
1353
1379
}
1354
1380
1355
1381
fiberToFiberInstanceMap . delete ( fiber ) ;
@@ -1358,13 +1384,6 @@ export function attach(
1358
1384
if (alternate !== null) {
1359
1385
fiberToFiberInstanceMap . delete ( alternate ) ;
1360
1386
}
1361
-
1362
- if (forceErrorForFiberIDs.has(fiberID)) {
1363
- forceErrorForFiberIDs . delete ( fiberID ) ;
1364
- if ( forceErrorForFiberIDs . size === 0 && setErrorHandler != null ) {
1365
- setErrorHandler ( shouldErrorFiberAlwaysNull ) ;
1366
- }
1367
- }
1368
1387
} ) ;
1369
1388
untrackFibersSet . clear ( ) ;
1370
1389
}
@@ -3504,7 +3523,7 @@ export function attach(
3504
3523
const DidCapture = 0b000000000000000000010000000 ;
3505
3524
isErrored =
3506
3525
( fiber . flags & DidCapture ) !== 0 ||
3507
- forceErrorForFiberIDs . get ( id ) === true ;
3526
+ ( devtoolsInstance . flags & FORCE_ERROR ) !== 0 ;
3508
3527
targetErrorBoundaryID = isErrored ? id : getNearestErrorBoundaryID ( fiber ) ;
3509
3528
} else {
3510
3529
targetErrorBoundaryID = getNearestErrorBoundaryID ( fiber ) ;
@@ -3553,7 +3572,7 @@ export function attach(
3553
3572
( ! isTimedOutSuspense ||
3554
3573
// If it's showing fallback because we previously forced it to,
3555
3574
// allow toggling it back to remove the fallback override.
3556
- forceFallbackForSuspenseIDs . has ( id ) ) ,
3575
+ ( devtoolsInstance . flags & FORCE_SUSPENSE_FALLBACK ) !== 0 ) ,
3557
3576
3558
3577
// Can view component source location.
3559
3578
canViewSource ,
@@ -4352,44 +4371,45 @@ export function attach(
4352
4371
return null ;
4353
4372
}
4354
4373
4355
- // Map of id and its force error status: true (error), false (toggled off),
4356
- // null (do nothing)
4357
- const forceErrorForFiberIDs = new Map< number | null , $FlowFixMe > ();
4374
+ let forceErrorCount = 0;
4358
4375
4359
- function shouldErrorFiberAccordingToMap(fiber: any) {
4376
+ function shouldErrorFiberAccordingToMap(fiber: any): null | boolean {
4360
4377
if ( typeof setErrorHandler !== 'function' ) {
4361
4378
throw new Error (
4362
4379
'Expected overrideError() to not get called for earlier React versions.' ,
4363
4380
) ;
4364
4381
}
4365
4382
4366
- const id = getFiberIDUnsafe(fiber);
4367
- if (id === null) {
4383
+ let fiberInstance = fiberToFiberInstanceMap.get(fiber);
4384
+ if (fiberInstance === undefined && fiber . alternate !== null ) {
4385
+ fiberInstance = fiberToFiberInstanceMap . get ( fiber . alternate ) ;
4386
+ }
4387
+ if (fiberInstance === undefined) {
4368
4388
return null ;
4369
4389
}
4370
4390
4371
- let status = null;
4372
- if (forceErrorForFiberIDs.has(id)) {
4373
- status = forceErrorForFiberIDs . get ( id ) ;
4374
- if ( status === false ) {
4375
- // TRICKY overrideError adds entries to this Map,
4376
- // so ideally it would be the method that clears them too,
4377
- // but that would break the functionality of the feature,
4378
- // since DevTools needs to tell React to act differently than it normally would
4379
- // (don't just re-render the failed boundary, but reset its errored state too).
4380
- // So we can only clear it after telling React to reset the state.
4381
- // Technically this is premature and we should schedule it for later,
4382
- // since the render could always fail without committing the updated error boundary,
4383
- // but since this is a DEV-only feature, the simplicity is worth the trade off.
4384
- forceErrorForFiberIDs . delete ( id ) ;
4385
-
4386
- if ( forceErrorForFiberIDs . size === 0 ) {
4387
- // Last override is gone. Switch React back to fast path.
4388
- setErrorHandler ( shouldErrorFiberAlwaysNull ) ;
4389
- }
4391
+ if (fiberInstance.flags & FORCE_ERROR_RESET ) {
4392
+ // TRICKY overrideError adds entries to this Map,
4393
+ // so ideally it would be the method that clears them too,
4394
+ // but that would break the functionality of the feature,
4395
+ // since DevTools needs to tell React to act differently than it normally would
4396
+ // (don't just re-render the failed boundary, but reset its errored state too).
4397
+ // So we can only clear it after telling React to reset the state.
4398
+ // Technically this is premature and we should schedule it for later,
4399
+ // since the render could always fail without committing the updated error boundary,
4400
+ // but since this is a DEV-only feature, the simplicity is worth the trade off.
4401
+ forceErrorCount -- ;
4402
+ fiberInstance . flags &= ~ FORCE_ERROR_RESET ;
4403
+ if ( forceErrorCount === 0 ) {
4404
+ // Last override is gone. Switch React back to fast path.
4405
+ setErrorHandler ( shouldErrorFiberAlwaysNull ) ;
4390
4406
}
4407
+ return false ;
4408
+ } else if (fiberInstance.flags & FORCE_ERROR ) {
4409
+ return true ;
4410
+ } else {
4411
+ return null ;
4391
4412
}
4392
- return status ;
4393
4413
}
4394
4414
4395
4415
function overrideError ( id : number , forceError : boolean ) {
@@ -4402,17 +4422,20 @@ export function attach(
4402
4422
) ;
4403
4423
}
4404
4424
4405
- forceErrorForFiberIDs.set(id, forceError);
4406
-
4407
- if (forceErrorForFiberIDs.size === 1) {
4408
- // First override is added. Switch React to slower path.
4409
- setErrorHandler ( shouldErrorFiberAccordingToMap ) ;
4410
- }
4411
-
4412
4425
const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4413
4426
if (devtoolsInstance === undefined) {
4414
4427
return ;
4415
4428
}
4429
+ if ((devtoolsInstance.flags & ( FORCE_ERROR | FORCE_ERROR_RESET ) ) === 0 ) {
4430
+ forceErrorCount ++ ;
4431
+ if ( forceErrorCount === 1 ) {
4432
+ // First override is added. Switch React to slower path.
4433
+ setErrorHandler ( shouldErrorFiberAccordingToMap ) ;
4434
+ }
4435
+ }
4436
+ devtoolsInstance . flags &= forceError ? ~ FORCE_ERROR_RESET : ~ FORCE_ERROR ;
4437
+ devtoolsInstance . flags |= forceError ? FORCE_ERROR : FORCE_ERROR_RESET ;
4438
+
4416
4439
if ( devtoolsInstance . kind === FIBER_INSTANCE ) {
4417
4440
const fiber = devtoolsInstance . data ;
4418
4441
scheduleUpdate ( fiber ) ;
@@ -4425,11 +4448,17 @@ export function attach(
4425
4448
return false ;
4426
4449
}
4427
4450
4428
- const forceFallbackForSuspenseIDs = new Set < number > () ;
4451
+ let forceFallbackCount = 0 ;
4429
4452
4430
4453
function shouldSuspendFiberAccordingToSet(fiber: any) {
4431
- const maybeID = getFiberIDUnsafe ( ( ( fiber : any ) : Fiber ) ) ;
4432
- return maybeID !== null && forceFallbackForSuspenseIDs . has ( maybeID ) ;
4454
+ let fiberInstance = fiberToFiberInstanceMap . get ( fiber ) ;
4455
+ if ( fiberInstance === undefined && fiber . alternate !== null ) {
4456
+ fiberInstance = fiberToFiberInstanceMap . get ( fiber . alternate ) ;
4457
+ }
4458
+ return (
4459
+ fiberInstance !== undefined &&
4460
+ ( fiberInstance . flags & FORCE_SUSPENSE_FALLBACK ) !== 0
4461
+ ) ;
4433
4462
}
4434
4463
4435
4464
function overrideSuspense(id: number, forceFallback: boolean) {
@@ -4441,24 +4470,31 @@ export function attach(
4441
4470
'Expected overrideSuspense() to not get called for earlier React versions.' ,
4442
4471
) ;
4443
4472
}
4473
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4474
+ if (devtoolsInstance === undefined) {
4475
+ return ;
4476
+ }
4477
+
4444
4478
if (forceFallback) {
4445
- forceFallbackForSuspenseIDs . add ( id ) ;
4446
- if ( forceFallbackForSuspenseIDs . size === 1 ) {
4447
- // First override is added. Switch React to slower path.
4448
- setSuspenseHandler ( shouldSuspendFiberAccordingToSet ) ;
4479
+ if ( ( devtoolsInstance . flags & FORCE_SUSPENSE_FALLBACK ) === 0 ) {
4480
+ devtoolsInstance . flags |= FORCE_SUSPENSE_FALLBACK ;
4481
+ forceFallbackCount ++ ;
4482
+ if ( forceFallbackCount === 1 ) {
4483
+ // First override is added. Switch React to slower path.
4484
+ setSuspenseHandler ( shouldSuspendFiberAccordingToSet ) ;
4485
+ }
4449
4486
}
4450
4487
} else {
4451
- forceFallbackForSuspenseIDs . delete ( id ) ;
4452
- if ( forceFallbackForSuspenseIDs . size === 0 ) {
4453
- // Last override is gone. Switch React back to fast path.
4454
- setSuspenseHandler ( shouldSuspendFiberAlwaysFalse ) ;
4488
+ if ( ( devtoolsInstance . flags & FORCE_SUSPENSE_FALLBACK ) !== 0 ) {
4489
+ devtoolsInstance . flags &= ~ FORCE_SUSPENSE_FALLBACK ;
4490
+ forceFallbackCount -- ;
4491
+ if ( forceFallbackCount === 0 ) {
4492
+ // Last override is gone. Switch React back to fast path.
4493
+ setSuspenseHandler ( shouldSuspendFiberAlwaysFalse ) ;
4494
+ }
4455
4495
}
4456
4496
}
4457
4497
4458
- const devtoolsInstance = idToDevToolsInstanceMap . get ( id ) ;
4459
- if ( devtoolsInstance === undefined ) {
4460
- return ;
4461
- }
4462
4498
if ( devtoolsInstance . kind === FIBER_INSTANCE ) {
4463
4499
const fiber = devtoolsInstance . data ;
4464
4500
scheduleUpdate ( fiber ) ;
@@ -4718,7 +4754,10 @@ export function attach(
4718
4754
4719
4755
function getComponentStackForFiber(fiber: Fiber): string | null {
4720
4756
// TODO: This should really just take an DevToolsInstance directly.
4721
- const fiberInstance = fiberToFiberInstanceMap . get ( fiber ) ;
4757
+ let fiberInstance = fiberToFiberInstanceMap . get ( fiber ) ;
4758
+ if ( fiberInstance === undefined && fiber . alternate !== null ) {
4759
+ fiberInstance = fiberToFiberInstanceMap . get ( fiber . alternate ) ;
4760
+ }
4722
4761
if ( fiberInstance === undefined ) {
4723
4762
// We're no longer tracking this instance.
4724
4763
return null ;
0 commit comments