@@ -42,6 +42,7 @@ describe('ReactErrorBoundaries', () => {
4242 PropTypes = require ( 'prop-types' ) ;
4343 ReactFeatureFlags = require ( 'shared/ReactFeatureFlags' ) ;
4444 ReactFeatureFlags . replayFailedUnitOfWorkWithInvokeGuardedCallback = false ;
45+ ReactFeatureFlags . skipUnmountedBoundaries = true ;
4546 ReactDOM = require ( 'react-dom' ) ;
4647 React = require ( 'react' ) ;
4748 act = require ( 'react-dom/test-utils' ) . unstable_concurrentAct ;
@@ -2473,4 +2474,173 @@ describe('ReactErrorBoundaries', () => {
24732474 'Caught an error: gotta catch em all.' ,
24742475 ) ;
24752476 } ) ;
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+ } ) ;
24762646} ) ;
0 commit comments