@@ -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,9 @@ export function attach(
893
902
args : $ReadOnlyArray < any > ,
894
903
) : void {
895
904
if ( type === 'error' ) {
896
- const maybeID = getFiberIDUnsafe ( fiber ) ;
905
+ const fiberInstance = fiberToFiberInstanceMap . get ( fiber ) ;
897
906
// if this is an error simulated by us to trigger error boundary, ignore
898
- if ( maybeID != null && forceErrorForFiberIDs . get ( maybeID ) === true ) {
907
+ if ( fiberInstance !== undefined && fiberInstance . flags & FORCE_ERROR ) {
899
908
return ;
900
909
}
901
910
}
@@ -1343,13 +1352,20 @@ export function attach(
1343
1352
}
1344
1353
1345
1354
untrackFibersSet.forEach(fiber => {
1346
- const fiberID = getFiberIDUnsafe ( fiber ) ;
1347
- if ( fiberID !== null ) {
1348
- idToDevToolsInstanceMap . delete ( fiberID ) ;
1355
+ const fiberInstance = fiberToFiberInstanceMap . get ( fiber ) ;
1356
+ if ( fiberInstance !== undefined ) {
1357
+ idToDevToolsInstanceMap . delete ( fiberInstance . id ) ;
1349
1358
1350
1359
// Also clear any errors/warnings associated with this fiber.
1351
- clearErrorsForElementID ( fiberID ) ;
1352
- clearWarningsForElementID ( fiberID ) ;
1360
+ clearErrorsForElementID ( fiberInstance . id ) ;
1361
+ clearWarningsForElementID ( fiberInstance . id ) ;
1362
+ if ( fiberInstance . flags & FORCE_ERROR ) {
1363
+ fiberInstance . flags &= ~ FORCE_ERROR ;
1364
+ forceErrorCount -- ;
1365
+ if ( forceErrorCount === 0 && setErrorHandler != null ) {
1366
+ setErrorHandler ( shouldErrorFiberAlwaysNull ) ;
1367
+ }
1368
+ }
1353
1369
}
1354
1370
1355
1371
fiberToFiberInstanceMap . delete ( fiber ) ;
@@ -1358,13 +1374,6 @@ export function attach(
1358
1374
if (alternate !== null) {
1359
1375
fiberToFiberInstanceMap . delete ( alternate ) ;
1360
1376
}
1361
-
1362
- if (forceErrorForFiberIDs.has(fiberID)) {
1363
- forceErrorForFiberIDs . delete ( fiberID ) ;
1364
- if ( forceErrorForFiberIDs . size === 0 && setErrorHandler != null ) {
1365
- setErrorHandler ( shouldErrorFiberAlwaysNull ) ;
1366
- }
1367
- }
1368
1377
} ) ;
1369
1378
untrackFibersSet . clear ( ) ;
1370
1379
}
@@ -3504,7 +3513,7 @@ export function attach(
3504
3513
const DidCapture = 0b000000000000000000010000000 ;
3505
3514
isErrored =
3506
3515
( fiber . flags & DidCapture ) !== 0 ||
3507
- forceErrorForFiberIDs . get ( id ) === true ;
3516
+ ( devtoolsInstance . flags & FORCE_ERROR ) !== 0 ;
3508
3517
targetErrorBoundaryID = isErrored ? id : getNearestErrorBoundaryID ( fiber ) ;
3509
3518
} else {
3510
3519
targetErrorBoundaryID = getNearestErrorBoundaryID ( fiber ) ;
@@ -3553,7 +3562,7 @@ export function attach(
3553
3562
( ! isTimedOutSuspense ||
3554
3563
// If it's showing fallback because we previously forced it to,
3555
3564
// allow toggling it back to remove the fallback override.
3556
- forceFallbackForSuspenseIDs . has ( id ) ) ,
3565
+ ( devtoolsInstance . flags & FORCE_SUSPENSE_FALLBACK ) !== 0 ) ,
3557
3566
3558
3567
// Can view component source location.
3559
3568
canViewSource ,
@@ -4352,44 +4361,42 @@ export function attach(
4352
4361
return null ;
4353
4362
}
4354
4363
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 > ();
4364
+ let forceErrorCount = 0;
4358
4365
4359
- function shouldErrorFiberAccordingToMap(fiber: any) {
4366
+ function shouldErrorFiberAccordingToMap(fiber: any): null | boolean {
4360
4367
if ( typeof setErrorHandler !== 'function' ) {
4361
4368
throw new Error (
4362
4369
'Expected overrideError() to not get called for earlier React versions.' ,
4363
4370
) ;
4364
4371
}
4365
4372
4366
- const id = getFiberIDUnsafe (fiber);
4367
- if (id === null ) {
4373
+ const fiberInstance = fiberToFiberInstanceMap.get (fiber);
4374
+ if (fiberInstance === undefined ) {
4368
4375
return null ;
4369
4376
}
4370
4377
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
- }
4378
+ if (fiberInstance.flags & FORCE_ERROR_RESET ) {
4379
+ // TRICKY overrideError adds entries to this Map,
4380
+ // so ideally it would be the method that clears them too,
4381
+ // but that would break the functionality of the feature,
4382
+ // since DevTools needs to tell React to act differently than it normally would
4383
+ // (don't just re-render the failed boundary, but reset its errored state too).
4384
+ // So we can only clear it after telling React to reset the state.
4385
+ // Technically this is premature and we should schedule it for later,
4386
+ // since the render could always fail without committing the updated error boundary,
4387
+ // but since this is a DEV-only feature, the simplicity is worth the trade off.
4388
+ forceErrorCount -- ;
4389
+ fiberInstance . flags &= ~ FORCE_ERROR_RESET ;
4390
+ if ( forceErrorCount === 0 ) {
4391
+ // Last override is gone. Switch React back to fast path.
4392
+ setErrorHandler ( shouldErrorFiberAlwaysNull ) ;
4390
4393
}
4394
+ return false ;
4395
+ } else if (fiberInstance.flags & FORCE_ERROR ) {
4396
+ return true ;
4397
+ } else {
4398
+ return null ;
4391
4399
}
4392
- return status ;
4393
4400
}
4394
4401
4395
4402
function overrideError ( id : number , forceError : boolean ) {
@@ -4402,17 +4409,20 @@ export function attach(
4402
4409
) ;
4403
4410
}
4404
4411
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
4412
const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4413
4413
if (devtoolsInstance === undefined) {
4414
4414
return ;
4415
4415
}
4416
+ if ((devtoolsInstance.flags & ( FORCE_ERROR | FORCE_ERROR_RESET ) ) === 0 ) {
4417
+ forceErrorCount ++ ;
4418
+ if ( forceErrorCount === 1 ) {
4419
+ // First override is added. Switch React to slower path.
4420
+ setErrorHandler ( shouldErrorFiberAccordingToMap ) ;
4421
+ }
4422
+ }
4423
+ devtoolsInstance . flags &= forceError ? ~ FORCE_ERROR_RESET : ~ FORCE_ERROR ;
4424
+ devtoolsInstance . flags |= forceError ? FORCE_ERROR : FORCE_ERROR_RESET ;
4425
+
4416
4426
if ( devtoolsInstance . kind === FIBER_INSTANCE ) {
4417
4427
const fiber = devtoolsInstance . data ;
4418
4428
scheduleUpdate ( fiber ) ;
@@ -4425,11 +4435,14 @@ export function attach(
4425
4435
return false ;
4426
4436
}
4427
4437
4428
- const forceFallbackForSuspenseIDs = new Set < number > () ;
4438
+ let forceFallbackCount = 0 ;
4429
4439
4430
4440
function shouldSuspendFiberAccordingToSet(fiber: any) {
4431
- const maybeID = getFiberIDUnsafe ( ( ( fiber : any ) : Fiber ) ) ;
4432
- return maybeID !== null && forceFallbackForSuspenseIDs . has ( maybeID ) ;
4441
+ const fiberInstance = fiberToFiberInstanceMap . get ( fiber ) ;
4442
+ return (
4443
+ fiberInstance !== undefined &&
4444
+ ( fiberInstance . flags & FORCE_SUSPENSE_FALLBACK ) !== 0
4445
+ ) ;
4433
4446
}
4434
4447
4435
4448
function overrideSuspense(id: number, forceFallback: boolean) {
@@ -4441,24 +4454,31 @@ export function attach(
4441
4454
'Expected overrideSuspense() to not get called for earlier React versions.' ,
4442
4455
) ;
4443
4456
}
4457
+ const devtoolsInstance = idToDevToolsInstanceMap.get(id);
4458
+ if (devtoolsInstance === undefined) {
4459
+ return ;
4460
+ }
4461
+
4444
4462
if (forceFallback) {
4445
- forceFallbackForSuspenseIDs . add ( id ) ;
4446
- if ( forceFallbackForSuspenseIDs . size === 1 ) {
4447
- // First override is added. Switch React to slower path.
4448
- setSuspenseHandler ( shouldSuspendFiberAccordingToSet ) ;
4463
+ if ( ( devtoolsInstance . flags & FORCE_SUSPENSE_FALLBACK ) === 0 ) {
4464
+ devtoolsInstance . flags |= FORCE_SUSPENSE_FALLBACK ;
4465
+ forceFallbackCount ++ ;
4466
+ if ( forceFallbackCount === 1 ) {
4467
+ // First override is added. Switch React to slower path.
4468
+ setSuspenseHandler ( shouldSuspendFiberAccordingToSet ) ;
4469
+ }
4449
4470
}
4450
4471
} else {
4451
- forceFallbackForSuspenseIDs . delete ( id ) ;
4452
- if ( forceFallbackForSuspenseIDs . size === 0 ) {
4453
- // Last override is gone. Switch React back to fast path.
4454
- setSuspenseHandler ( shouldSuspendFiberAlwaysFalse ) ;
4472
+ if ( ( devtoolsInstance . flags & FORCE_SUSPENSE_FALLBACK ) !== 0 ) {
4473
+ devtoolsInstance . flags &= ~ FORCE_SUSPENSE_FALLBACK ;
4474
+ forceFallbackCount -- ;
4475
+ if ( forceFallbackCount === 0 ) {
4476
+ // Last override is gone. Switch React back to fast path.
4477
+ setSuspenseHandler ( shouldSuspendFiberAlwaysFalse ) ;
4478
+ }
4455
4479
}
4456
4480
}
4457
4481
4458
- const devtoolsInstance = idToDevToolsInstanceMap . get ( id ) ;
4459
- if ( devtoolsInstance === undefined ) {
4460
- return ;
4461
- }
4462
4482
if ( devtoolsInstance . kind === FIBER_INSTANCE ) {
4463
4483
const fiber = devtoolsInstance . data ;
4464
4484
scheduleUpdate ( fiber ) ;
0 commit comments