@@ -1632,7 +1632,7 @@ describe('ReactLazy', () => {
16321632 expect ( root ) . toMatchRenderedOutput ( 'ba' ) ;
16331633 } ) ;
16341634
1635- it ( 'does not destroy layout effects twice' , async ( ) => {
1635+ it ( 'does not destroy layout effects twice when hidden child is removed ' , async ( ) => {
16361636 function ChildA ( { label} ) {
16371637 React . useLayoutEffect ( ( ) => {
16381638 Scheduler . unstable_yieldValue ( 'Did mount: ' + label ) ;
@@ -1686,7 +1686,7 @@ describe('ReactLazy', () => {
16861686 expect ( root ) . toMatchRenderedOutput ( 'B' ) ;
16871687 } ) ;
16881688
1689- it ( 'does not call componentWillUnmount twice' , async ( ) => {
1689+ it ( 'does not call componentWillUnmount twice when hidden child is removed ' , async ( ) => {
16901690 class ChildA extends React . Component {
16911691 componentDidMount ( ) {
16921692 Scheduler . unstable_yieldValue ( 'Did mount: ' + this . props . label ) ;
@@ -1743,4 +1743,120 @@ describe('ReactLazy', () => {
17431743 expect ( Scheduler ) . toFlushAndYield ( [ 'B' , 'Did mount: B' ] ) ;
17441744 expect ( root ) . toMatchRenderedOutput ( 'B' ) ;
17451745 } ) ;
1746+
1747+ it ( 'does not destroy layout effects twice when parent suspense is removed' , async ( ) => {
1748+ function ChildA ( { label} ) {
1749+ React . useLayoutEffect ( ( ) => {
1750+ Scheduler . unstable_yieldValue ( 'Did mount: ' + label ) ;
1751+ return ( ) => {
1752+ Scheduler . unstable_yieldValue ( 'Will unmount: ' + label ) ;
1753+ } ;
1754+ } , [ ] ) ;
1755+ return < Text text = { label } /> ;
1756+ }
1757+ function ChildB ( { label} ) {
1758+ React . useLayoutEffect ( ( ) => {
1759+ Scheduler . unstable_yieldValue ( 'Did mount: ' + label ) ;
1760+ return ( ) => {
1761+ Scheduler . unstable_yieldValue ( 'Will unmount: ' + label ) ;
1762+ } ;
1763+ } , [ ] ) ;
1764+ return < Text text = { label } /> ;
1765+ }
1766+ const LazyChildA = lazy ( ( ) => fakeImport ( ChildA ) ) ;
1767+ const LazyChildB = lazy ( ( ) => fakeImport ( ChildB ) ) ;
1768+
1769+ function Parent ( { swap} ) {
1770+ return (
1771+ < Suspense fallback = { < Text text = "Loading..." /> } >
1772+ { swap ? < LazyChildB label = "B" /> : < LazyChildA label = "A" /> }
1773+ </ Suspense >
1774+ ) ;
1775+ }
1776+
1777+ const root = ReactTestRenderer . create ( < Parent swap = { false } /> , {
1778+ unstable_isConcurrent : true ,
1779+ } ) ;
1780+
1781+ expect ( Scheduler ) . toFlushAndYield ( [ 'Loading...' ] ) ;
1782+
1783+ await LazyChildA ;
1784+ expect ( Scheduler ) . toFlushAndYield ( [ 'A' , 'Did mount: A' ] ) ;
1785+ expect ( root ) . toMatchRenderedOutput ( 'A' ) ;
1786+
1787+ // Swap the position of A and B
1788+ root . unstable_flushSync ( ( ) => {
1789+ root . update ( < Parent swap = { true } /> ) ;
1790+ } ) ;
1791+ expect ( Scheduler ) . toHaveYielded ( [ 'Loading...' , 'Will unmount: A' ] ) ;
1792+ expect ( root ) . toMatchRenderedOutput ( 'Loading...' ) ;
1793+
1794+ // Destroy the whole tree, including the hidden A
1795+ root . unstable_flushSync ( ( ) => {
1796+ root . update ( < h1 > Hello</ h1 > ) ;
1797+ } ) ;
1798+ expect ( Scheduler ) . toFlushAndYield ( [ ] ) ;
1799+ expect ( root ) . toMatchRenderedOutput ( < h1 > Hello</ h1 > ) ;
1800+ } ) ;
1801+
1802+ it ( 'does not call componentWillUnmount twice when parent suspense is removed' , async ( ) => {
1803+ class ChildA extends React . Component {
1804+ componentDidMount ( ) {
1805+ Scheduler . unstable_yieldValue ( 'Did mount: ' + this . props . label ) ;
1806+ }
1807+ componentWillUnmount ( ) {
1808+ Scheduler . unstable_yieldValue ( 'Will unmount: ' + this . props . label ) ;
1809+ }
1810+ render ( ) {
1811+ return < Text text = { this . props . label } /> ;
1812+ }
1813+ }
1814+
1815+ class ChildB extends React . Component {
1816+ componentDidMount ( ) {
1817+ Scheduler . unstable_yieldValue ( 'Did mount: ' + this . props . label ) ;
1818+ }
1819+ componentWillUnmount ( ) {
1820+ Scheduler . unstable_yieldValue ( 'Will unmount: ' + this . props . label ) ;
1821+ }
1822+ render ( ) {
1823+ return < Text text = { this . props . label } /> ;
1824+ }
1825+ }
1826+
1827+ const LazyChildA = lazy ( ( ) => fakeImport ( ChildA ) ) ;
1828+ const LazyChildB = lazy ( ( ) => fakeImport ( ChildB ) ) ;
1829+
1830+ function Parent ( { swap} ) {
1831+ return (
1832+ < Suspense fallback = { < Text text = "Loading..." /> } >
1833+ { swap ? < LazyChildB label = "B" /> : < LazyChildA label = "A" /> }
1834+ </ Suspense >
1835+ ) ;
1836+ }
1837+
1838+ const root = ReactTestRenderer . create ( < Parent swap = { false } /> , {
1839+ unstable_isConcurrent : true ,
1840+ } ) ;
1841+
1842+ expect ( Scheduler ) . toFlushAndYield ( [ 'Loading...' ] ) ;
1843+
1844+ await LazyChildA ;
1845+ expect ( Scheduler ) . toFlushAndYield ( [ 'A' , 'Did mount: A' ] ) ;
1846+ expect ( root ) . toMatchRenderedOutput ( 'A' ) ;
1847+
1848+ // Swap the position of A and B
1849+ root . unstable_flushSync ( ( ) => {
1850+ root . update ( < Parent swap = { true } /> ) ;
1851+ } ) ;
1852+ expect ( Scheduler ) . toHaveYielded ( [ 'Loading...' , 'Will unmount: A' ] ) ;
1853+ expect ( root ) . toMatchRenderedOutput ( 'Loading...' ) ;
1854+
1855+ // Destroy the whole tree, including the hidden A
1856+ root . unstable_flushSync ( ( ) => {
1857+ root . update ( < h1 > Hello</ h1 > ) ;
1858+ } ) ;
1859+ expect ( Scheduler ) . toFlushAndYield ( [ '' ] ) ;
1860+ expect ( root ) . toMatchRenderedOutput ( < h1 > Hello</ h1 > ) ;
1861+ } ) ;
17461862} ) ;
0 commit comments