@@ -2424,6 +2424,60 @@ describe('DOMPluginEventSystem', () => {
2424
2424
expect ( log ) . toEqual ( [ 'beforeblur' , 'afterblur' ] ) ;
2425
2425
} ) ;
2426
2426
2427
+ // @gate experimental
2428
+ it ( 'beforeblur should skip handlers from a deleted subtree after the focused element is unmounted' , ( ) => {
2429
+ const onBeforeBlur = jest . fn ( ) ;
2430
+ const innerRef = React . createRef ( ) ;
2431
+ const innerRef2 = React . createRef ( ) ;
2432
+ const setBeforeBlurHandle = ReactDOM . unstable_createEventHandle (
2433
+ 'beforeblur' ,
2434
+ ) ;
2435
+ const ref2 = React . createRef ( ) ;
2436
+
2437
+ const Component = ( { show} ) => {
2438
+ const ref = React . useRef ( null ) ;
2439
+
2440
+ React . useEffect ( ( ) => {
2441
+ const clear1 = setBeforeBlurHandle ( ref . current , onBeforeBlur ) ;
2442
+ let clear2 ;
2443
+ if ( ref2 . current ) {
2444
+ clear2 = setBeforeBlurHandle ( ref2 . current , onBeforeBlur ) ;
2445
+ }
2446
+
2447
+ return ( ) => {
2448
+ clear1 ( ) ;
2449
+ if ( clear2 ) {
2450
+ clear2 ( ) ;
2451
+ }
2452
+ } ;
2453
+ } ) ;
2454
+
2455
+ return (
2456
+ < div ref = { ref } >
2457
+ { show && (
2458
+ < div ref = { ref2 } >
2459
+ < input ref = { innerRef } />
2460
+ </ div >
2461
+ ) }
2462
+ < div ref = { innerRef2 } />
2463
+ </ div >
2464
+ ) ;
2465
+ } ;
2466
+
2467
+ ReactDOM . render ( < Component show = { true } /> , container ) ;
2468
+ Scheduler . unstable_flushAll ( ) ;
2469
+
2470
+ const inner = innerRef . current ;
2471
+ const target = createEventTarget ( inner ) ;
2472
+ target . focus ( ) ;
2473
+ expect ( onBeforeBlur ) . toHaveBeenCalledTimes ( 0 ) ;
2474
+
2475
+ ReactDOM . render ( < Component show = { false } /> , container ) ;
2476
+ Scheduler . unstable_flushAll ( ) ;
2477
+
2478
+ expect ( onBeforeBlur ) . toHaveBeenCalledTimes ( 1 ) ;
2479
+ } ) ;
2480
+
2427
2481
// @gate experimental
2428
2482
it ( 'beforeblur and afterblur are called after a focused element is suspended' , ( ) => {
2429
2483
const log = [ ] ;
@@ -2510,6 +2564,87 @@ describe('DOMPluginEventSystem', () => {
2510
2564
document . body . removeChild ( container2 ) ;
2511
2565
} ) ;
2512
2566
2567
+ // @gate experimental
2568
+ it ( 'beforeblur should skip handlers from a deleted subtree after the focused element is suspended' , ( ) => {
2569
+ const onBeforeBlur = jest . fn ( ) ;
2570
+ const innerRef = React . createRef ( ) ;
2571
+ const innerRef2 = React . createRef ( ) ;
2572
+ const setBeforeBlurHandle = ReactDOM . unstable_createEventHandle (
2573
+ 'beforeblur' ,
2574
+ ) ;
2575
+ const ref2 = React . createRef ( ) ;
2576
+ const Suspense = React . Suspense ;
2577
+ let suspend = false ;
2578
+ let resolve ;
2579
+ const promise = new Promise (
2580
+ resolvePromise => ( resolve = resolvePromise ) ,
2581
+ ) ;
2582
+
2583
+ function Child ( ) {
2584
+ if ( suspend ) {
2585
+ throw promise ;
2586
+ } else {
2587
+ return < input ref = { innerRef } /> ;
2588
+ }
2589
+ }
2590
+
2591
+ const Component = ( ) => {
2592
+ const ref = React . useRef ( null ) ;
2593
+
2594
+ React . useEffect ( ( ) => {
2595
+ const clear1 = setBeforeBlurHandle ( ref . current , onBeforeBlur ) ;
2596
+ let clear2 ;
2597
+ if ( ref2 . current ) {
2598
+ clear2 = setBeforeBlurHandle ( ref2 . current , onBeforeBlur ) ;
2599
+ }
2600
+
2601
+ return ( ) => {
2602
+ clear1 ( ) ;
2603
+ if ( clear2 ) {
2604
+ clear2 ( ) ;
2605
+ }
2606
+ } ;
2607
+ } ) ;
2608
+
2609
+ return (
2610
+ < div ref = { ref } >
2611
+ < Suspense fallback = "Loading..." >
2612
+ < div ref = { ref2 } >
2613
+ < Child />
2614
+ </ div >
2615
+ </ Suspense >
2616
+ < div ref = { innerRef2 } />
2617
+ </ div >
2618
+ ) ;
2619
+ } ;
2620
+
2621
+ const container2 = document . createElement ( 'div' ) ;
2622
+ document . body . appendChild ( container2 ) ;
2623
+
2624
+ const root = ReactDOM . createRoot ( container2 ) ;
2625
+
2626
+ act ( ( ) => {
2627
+ root . render ( < Component /> ) ;
2628
+ } ) ;
2629
+ jest . runAllTimers ( ) ;
2630
+
2631
+ const inner = innerRef . current ;
2632
+ const target = createEventTarget ( inner ) ;
2633
+ target . focus ( ) ;
2634
+ expect ( onBeforeBlur ) . toHaveBeenCalledTimes ( 0 ) ;
2635
+
2636
+ suspend = true ;
2637
+ act ( ( ) => {
2638
+ root . render ( < Component /> ) ;
2639
+ } ) ;
2640
+ jest . runAllTimers ( ) ;
2641
+
2642
+ expect ( onBeforeBlur ) . toHaveBeenCalledTimes ( 1 ) ;
2643
+ resolve ( ) ;
2644
+
2645
+ document . body . removeChild ( container2 ) ;
2646
+ } ) ;
2647
+
2513
2648
// @gate experimental
2514
2649
it ( 'regression: does not fire beforeblur/afterblur if target is already hidden' , ( ) => {
2515
2650
const Suspense = React . Suspense ;
0 commit comments