@@ -68,9 +68,17 @@ let helpersByRoot: Map<FiberRoot, RendererHelpers> = new Map();
68
68
69
69
// We keep track of mounted roots so we can schedule updates.
70
70
let mountedRoots : Set < FiberRoot > = new Set ( ) ;
71
- // If a root captures an error, we add its element to this Map so we can retry on edit.
72
- let failedRoots : Map < FiberRoot , ReactNodeList > = new Map ( ) ;
73
- let didSomeRootFailOnMount = false ;
71
+ // If a root captures an error, we remember it so we can retry on edit.
72
+ let failedRoots : Set < FiberRoot > = new Set ( ) ;
73
+
74
+ // In environments that support WeakMap, we also remember the last element for every root.
75
+ // It needs to be weak because we do this even for roots that failed to mount.
76
+ // If there is no WeakMap, we won't attempt to do retrying.
77
+ // $FlowIssue
78
+ let rootElements : WeakMap < any , ReactNodeList > | null = // $FlowIssue
79
+ typeof WeakMap === 'function' ? new WeakMap ( ) : null ;
80
+
81
+ let isPerformingRefresh = false ;
74
82
75
83
function computeFullKey ( signature : Signature ) : string {
76
84
if ( signature . fullKey !== null ) {
@@ -171,11 +179,20 @@ function cloneSet<T>(set: Set<T>): Set<T> {
171
179
}
172
180
173
181
export function performReactRefresh(): RefreshUpdate | null {
174
- if ( __DEV__ ) {
175
- if ( pendingUpdates . length === 0 ) {
176
- return null ;
177
- }
182
+ if ( ! __DEV__ ) {
183
+ throw new Error (
184
+ 'Unexpected call to React Refresh in a production environment.' ,
185
+ ) ;
186
+ }
187
+ if (pendingUpdates.length === 0) {
188
+ return null ;
189
+ }
190
+ if (isPerformingRefresh) {
191
+ return null ;
192
+ }
178
193
194
+ isPerformingRefresh = true;
195
+ try {
179
196
const staleFamilies = new Set ( ) ;
180
197
const updatedFamilies = new Set ( ) ;
181
198
@@ -216,17 +233,27 @@ export function performReactRefresh(): RefreshUpdate | null {
216
233
// If we don't do this, there is a risk they will be mutated while
217
234
// we iterate over them. For example, trying to recover a failed root
218
235
// may cause another root to be added to the failed list -- an infinite loop.
219
- let failedRootsSnapshot = cloneMap (failedRoots);
236
+ let failedRootsSnapshot = cloneSet ( failedRoots ) ;
220
237
let mountedRootsSnapshot = cloneSet ( mountedRoots ) ;
221
238
let helpersByRootSnapshot = cloneMap ( helpersByRoot ) ;
222
239
223
- failedRootsSnapshot.forEach((element, root) => {
240
+ failedRootsSnapshot . forEach ( root => {
224
241
const helpers = helpersByRootSnapshot . get ( root ) ;
225
242
if ( helpers === undefined ) {
226
243
throw new Error (
227
244
'Could not find helpers for a root. This is a bug in React Refresh.' ,
228
245
) ;
229
246
}
247
+ if ( ! failedRoots . has ( root ) ) {
248
+ // No longer failed.
249
+ }
250
+ if ( rootElements === null ) {
251
+ return ;
252
+ }
253
+ if ( ! rootElements . has ( root ) ) {
254
+ return ;
255
+ }
256
+ const element = rootElements . get ( root ) ;
230
257
try {
231
258
helpers . scheduleRoot ( root , element ) ;
232
259
} catch ( err ) {
@@ -244,6 +271,9 @@ export function performReactRefresh(): RefreshUpdate | null {
244
271
'Could not find helpers for a root. This is a bug in React Refresh.' ,
245
272
) ;
246
273
}
274
+ if ( ! mountedRoots . has ( root ) ) {
275
+ // No longer mounted.
276
+ }
247
277
try {
248
278
helpers . scheduleRefresh ( root , update ) ;
249
279
} catch ( err ) {
@@ -258,10 +288,8 @@ export function performReactRefresh(): RefreshUpdate | null {
258
288
throw firstError ;
259
289
}
260
290
return update;
261
- } else {
262
- throw new Error (
263
- 'Unexpected call to React Refresh in a production environment.' ,
264
- ) ;
291
+ } finally {
292
+ isPerformingRefresh = false ;
265
293
}
266
294
}
267
295
@@ -411,6 +439,11 @@ export function injectIntoGlobalHook(globalObject: any): void {
411
439
inject ( injected ) {
412
440
return nextID ++ ;
413
441
} ,
442
+ onScheduleFiberRoot (
443
+ id : number ,
444
+ root : FiberRoot ,
445
+ children : ReactNodeList ,
446
+ ) { } ,
414
447
onCommitFiberRoot (
415
448
id : number ,
416
449
root : FiberRoot ,
@@ -437,6 +470,22 @@ export function injectIntoGlobalHook(globalObject: any): void {
437
470
438
471
// We also want to track currently mounted roots.
439
472
const oldOnCommitFiberRoot = hook . onCommitFiberRoot ;
473
+ const oldOnScheduleFiberRoot = hook . onScheduleFiberRoot || ( ( ) => { } ) ;
474
+ hook . onScheduleFiberRoot = function (
475
+ id : number ,
476
+ root : FiberRoot ,
477
+ children : mixed ,
478
+ ) {
479
+ if ( ! isPerformingRefresh ) {
480
+ // If it was intentionally scheduled, don't attempt to restore.
481
+ // This includes intentionally scheduled unmounts.
482
+ failedRoots . delete ( root ) ;
483
+ if ( rootElements !== null ) {
484
+ rootElements . set ( root , children ) ;
485
+ }
486
+ }
487
+ return oldOnScheduleFiberRoot . apply ( this , arguments ) ;
488
+ } ;
440
489
hook . onCommitFiberRoot = function (
441
490
id : number ,
442
491
root : FiberRoot ,
@@ -476,27 +525,14 @@ export function injectIntoGlobalHook(globalObject: any): void {
476
525
mountedRoots . delete ( root ) ;
477
526
if ( didError ) {
478
527
// We'll remount it on future edits.
479
- // Remember what was rendered so we can restore it.
480
- failedRoots . set ( root , alternate . memoizedState . element ) ;
528
+ failedRoots . add ( root ) ;
481
529
} else {
482
530
helpersByRoot . delete ( root ) ;
483
531
}
484
532
} else if ( ! wasMounted && ! isMounted ) {
485
- if ( didError && ! failedRoots . has ( root ) ) {
486
- // The root had an error during the initial mount.
487
- // We can't read its last element from the memoized state
488
- // because there was no previously committed alternate.
489
- // Ideally, it would be nice if we had a way to extract
490
- // the last attempted rendered element, but accessing the update queue
491
- // would tie this package too closely to the reconciler version.
492
- // So instead, we just set a flag.
493
- // TODO: Maybe we could fix this as the same time as when we fix
494
- // DevTools to not depend on `alternate.memoizedState.element`.
495
- didSomeRootFailOnMount = true ;
496
- } else if ( ! didError && failedRoots . has ( root ) ) {
497
- // The error is fixed but the component is still unmounted.
498
- // This means that the unmount was not caused by a failed refresh.
499
- failedRoots . delete ( root ) ;
533
+ if ( didError ) {
534
+ // We'll remount it on future edits.
535
+ failedRoots . add ( root ) ;
500
536
}
501
537
}
502
538
} else {
@@ -514,7 +550,8 @@ export function injectIntoGlobalHook(globalObject: any): void {
514
550
}
515
551
516
552
export function hasUnrecoverableErrors ( ) {
517
- return didSomeRootFailOnMount ;
553
+ // TODO: delete this after removing dependency in RN.
554
+ return false ;
518
555
}
519
556
520
557
// Exposed for testing.
0 commit comments