@@ -42,6 +42,7 @@ describe('ReactErrorBoundaries', () => {
42
42
PropTypes = require ( 'prop-types' ) ;
43
43
ReactFeatureFlags = require ( 'shared/ReactFeatureFlags' ) ;
44
44
ReactFeatureFlags . replayFailedUnitOfWorkWithInvokeGuardedCallback = false ;
45
+ ReactFeatureFlags . skipUnmountedBoundaries = true ;
45
46
ReactDOM = require ( 'react-dom' ) ;
46
47
React = require ( 'react' ) ;
47
48
act = require ( 'react-dom/test-utils' ) . unstable_concurrentAct ;
@@ -2473,4 +2474,173 @@ describe('ReactErrorBoundaries', () => {
2473
2474
'Caught an error: gotta catch em all.' ,
2474
2475
) ;
2475
2476
} ) ;
2477
+
2478
+ it ( 'catches errors thrown in componentWillUnmount' , ( ) => {
2479
+ class LocalErrorBoundary extends React . Component {
2480
+ state = { error : null } ;
2481
+ static getDerivedStateFromError ( error ) {
2482
+ Scheduler . unstable_yieldValue (
2483
+ `ErrorBoundary static getDerivedStateFromError` ,
2484
+ ) ;
2485
+ return { error} ;
2486
+ }
2487
+ render ( ) {
2488
+ const { children, id, fallbackID} = this . props ;
2489
+ const { error} = this . state ;
2490
+ if ( error ) {
2491
+ Scheduler . unstable_yieldValue ( `${ id } render error` ) ;
2492
+ return < Component id = { fallbackID } /> ;
2493
+ }
2494
+ Scheduler . unstable_yieldValue ( `${ id } render success` ) ;
2495
+ return children || null ;
2496
+ }
2497
+ }
2498
+
2499
+ class Component extends React . Component {
2500
+ render ( ) {
2501
+ const { id} = this . props ;
2502
+ Scheduler . unstable_yieldValue ( 'Component render ' + id ) ;
2503
+ return id ;
2504
+ }
2505
+ }
2506
+
2507
+ class LocalBrokenComponentWillUnmount extends React . Component {
2508
+ componentWillUnmount ( ) {
2509
+ Scheduler . unstable_yieldValue (
2510
+ 'BrokenComponentWillUnmount componentWillUnmount' ,
2511
+ ) ;
2512
+ throw Error ( 'Expected' ) ;
2513
+ }
2514
+
2515
+ render ( ) {
2516
+ Scheduler . unstable_yieldValue ( 'BrokenComponentWillUnmount render' ) ;
2517
+ return 'broken' ;
2518
+ }
2519
+ }
2520
+
2521
+ const container = document . createElement ( 'div' ) ;
2522
+
2523
+ ReactDOM . render (
2524
+ < LocalErrorBoundary id = "OuterBoundary" fallbackID = "OuterFallback" >
2525
+ < Component id = "sibling" />
2526
+ < LocalErrorBoundary id = "InnerBoundary" fallbackID = "InnerFallback" >
2527
+ < LocalBrokenComponentWillUnmount />
2528
+ </ LocalErrorBoundary >
2529
+ </ LocalErrorBoundary > ,
2530
+ container ,
2531
+ ) ;
2532
+
2533
+ expect ( container . firstChild . textContent ) . toBe ( 'sibling' ) ;
2534
+ expect ( container . lastChild . textContent ) . toBe ( 'broken' ) ;
2535
+ expect ( Scheduler ) . toHaveYielded ( [
2536
+ 'OuterBoundary render success' ,
2537
+ 'Component render sibling' ,
2538
+ 'InnerBoundary render success' ,
2539
+ 'BrokenComponentWillUnmount render' ,
2540
+ ] ) ;
2541
+
2542
+ ReactDOM . render (
2543
+ < LocalErrorBoundary id = "OuterBoundary" fallbackID = "OuterFallback" >
2544
+ < Component id = "sibling" />
2545
+ </ LocalErrorBoundary > ,
2546
+ container ,
2547
+ ) ;
2548
+
2549
+ // React should skip over the unmounting boundary and find the nearest still-mounted boundary.
2550
+ expect ( container . firstChild . textContent ) . toBe ( 'OuterFallback' ) ;
2551
+ expect ( container . lastChild . textContent ) . toBe ( 'OuterFallback' ) ;
2552
+ expect ( Scheduler ) . toHaveYielded ( [
2553
+ 'OuterBoundary render success' ,
2554
+ 'Component render sibling' ,
2555
+ 'BrokenComponentWillUnmount componentWillUnmount' ,
2556
+ 'ErrorBoundary static getDerivedStateFromError' ,
2557
+ 'OuterBoundary render error' ,
2558
+ 'Component render OuterFallback' ,
2559
+ ] ) ;
2560
+ } ) ;
2561
+
2562
+ it ( 'catches errors thrown while detaching refs' , ( ) => {
2563
+ class LocalErrorBoundary extends React . Component {
2564
+ state = { error : null } ;
2565
+ static getDerivedStateFromError ( error ) {
2566
+ Scheduler . unstable_yieldValue (
2567
+ `ErrorBoundary static getDerivedStateFromError` ,
2568
+ ) ;
2569
+ return { error} ;
2570
+ }
2571
+ render ( ) {
2572
+ const { children, id, fallbackID} = this . props ;
2573
+ const { error} = this . state ;
2574
+ if ( error ) {
2575
+ Scheduler . unstable_yieldValue ( `${ id } render error` ) ;
2576
+ return < Component id = { fallbackID } /> ;
2577
+ }
2578
+ Scheduler . unstable_yieldValue ( `${ id } render success` ) ;
2579
+ return children || null ;
2580
+ }
2581
+ }
2582
+
2583
+ class Component extends React . Component {
2584
+ render ( ) {
2585
+ const { id} = this . props ;
2586
+ Scheduler . unstable_yieldValue ( 'Component render ' + id ) ;
2587
+ return id ;
2588
+ }
2589
+ }
2590
+
2591
+ class LocalBrokenCallbackRef extends React . Component {
2592
+ _ref = ref => {
2593
+ Scheduler . unstable_yieldValue ( 'LocalBrokenCallbackRef ref ' + ! ! ref ) ;
2594
+ if ( ref === null ) {
2595
+ throw Error ( 'Expected' ) ;
2596
+ }
2597
+ } ;
2598
+
2599
+ render ( ) {
2600
+ Scheduler . unstable_yieldValue ( 'LocalBrokenCallbackRef render' ) ;
2601
+ return < div ref = { this . _ref } > ref</ div > ;
2602
+ }
2603
+ }
2604
+
2605
+ const container = document . createElement ( 'div' ) ;
2606
+
2607
+ ReactDOM . render (
2608
+ < LocalErrorBoundary id = "OuterBoundary" fallbackID = "OuterFallback" >
2609
+ < Component id = "sibling" />
2610
+ < LocalErrorBoundary id = "InnerBoundary" fallbackID = "InnerFallback" >
2611
+ < LocalBrokenCallbackRef />
2612
+ </ LocalErrorBoundary >
2613
+ </ LocalErrorBoundary > ,
2614
+ container ,
2615
+ ) ;
2616
+
2617
+ expect ( container . firstChild . textContent ) . toBe ( 'sibling' ) ;
2618
+ expect ( container . lastChild . textContent ) . toBe ( 'ref' ) ;
2619
+ expect ( Scheduler ) . toHaveYielded ( [
2620
+ 'OuterBoundary render success' ,
2621
+ 'Component render sibling' ,
2622
+ 'InnerBoundary render success' ,
2623
+ 'LocalBrokenCallbackRef render' ,
2624
+ 'LocalBrokenCallbackRef ref true' ,
2625
+ ] ) ;
2626
+
2627
+ ReactDOM . render (
2628
+ < LocalErrorBoundary id = "OuterBoundary" fallbackID = "OuterFallback" >
2629
+ < Component id = "sibling" />
2630
+ </ LocalErrorBoundary > ,
2631
+ container ,
2632
+ ) ;
2633
+
2634
+ // React should skip over the unmounting boundary and find the nearest still-mounted boundary.
2635
+ expect ( container . firstChild . textContent ) . toBe ( 'OuterFallback' ) ;
2636
+ expect ( container . lastChild . textContent ) . toBe ( 'OuterFallback' ) ;
2637
+ expect ( Scheduler ) . toHaveYielded ( [
2638
+ 'OuterBoundary render success' ,
2639
+ 'Component render sibling' ,
2640
+ 'LocalBrokenCallbackRef ref false' ,
2641
+ 'ErrorBoundary static getDerivedStateFromError' ,
2642
+ 'OuterBoundary render error' ,
2643
+ 'Component render OuterFallback' ,
2644
+ ] ) ;
2645
+ } ) ;
2476
2646
} ) ;
0 commit comments