@@ -28,16 +28,20 @@ describe('ReactErrorBoundaries', () => {
2828 let BrokenComponentWillMountErrorBoundary ;
2929 let BrokenComponentDidMountErrorBoundary ;
3030 let BrokenRender ;
31+ let BrokenUseEffect ;
32+ let BrokenUseLayoutEffect ;
3133 let ErrorBoundary ;
3234 let ErrorMessage ;
3335 let NoopErrorBoundary ;
3436 let RetryErrorBoundary ;
3537 let Normal ;
3638
3739 beforeEach ( ( ) => {
40+ jest . useFakeTimers ( ) ;
3841 jest . resetModules ( ) ;
3942 PropTypes = require ( 'prop-types' ) ;
4043 ReactFeatureFlags = require ( 'shared/ReactFeatureFlags' ) ;
44+ ReactFeatureFlags . enableHooks = true ;
4145 ReactFeatureFlags . replayFailedUnitOfWorkWithInvokeGuardedCallback = false ;
4246 ReactDOM = require ( 'react-dom' ) ;
4347 React = require ( 'react' ) ;
@@ -386,6 +390,28 @@ describe('ReactErrorBoundaries', () => {
386390 }
387391 } ;
388392
393+ BrokenUseEffect = props => {
394+ log . push ( 'BrokenUseEffect render' ) ;
395+
396+ React . useEffect ( ( ) => {
397+ log . push ( 'BrokenUseEffect useEffect [!]' ) ;
398+ throw new Error ( 'Hello' ) ;
399+ } ) ;
400+
401+ return props . children ;
402+ } ;
403+
404+ BrokenUseLayoutEffect = props => {
405+ log . push ( 'BrokenUseLayoutEffect render' ) ;
406+
407+ React . useLayoutEffect ( ( ) => {
408+ log . push ( 'BrokenUseLayoutEffect useLayoutEffect [!]' ) ;
409+ throw new Error ( 'Hello' ) ;
410+ } ) ;
411+
412+ return props . children ;
413+ } ;
414+
389415 NoopErrorBoundary = class extends React . Component {
390416 constructor ( props ) {
391417 super ( props ) ;
@@ -1795,6 +1821,67 @@ describe('ReactErrorBoundaries', () => {
17951821 expect ( log ) . toEqual ( [ 'ErrorBoundary componentWillUnmount' ] ) ;
17961822 } ) ;
17971823
1824+ it ( 'catches errors in useEffect' , ( ) => {
1825+ const container = document . createElement ( 'div' ) ;
1826+ ReactDOM . render (
1827+ < ErrorBoundary >
1828+ < BrokenUseEffect > Initial value</ BrokenUseEffect >
1829+ </ ErrorBoundary > ,
1830+ container ,
1831+ ) ;
1832+ expect ( log ) . toEqual ( [
1833+ 'ErrorBoundary constructor' ,
1834+ 'ErrorBoundary componentWillMount' ,
1835+ 'ErrorBoundary render success' ,
1836+ 'BrokenUseEffect render' ,
1837+ 'ErrorBoundary componentDidMount' ,
1838+ ] ) ;
1839+
1840+ expect ( container . firstChild . textContent ) . toBe ( 'Initial value' ) ;
1841+ log . length = 0 ;
1842+
1843+ jest . runAllTimers ( ) ;
1844+
1845+ // Flush passive effects and handle the error
1846+ expect ( log ) . toEqual ( [
1847+ 'BrokenUseEffect useEffect [!]' ,
1848+ // Handle the error
1849+ 'ErrorBoundary static getDerivedStateFromError' ,
1850+ 'ErrorBoundary componentWillUpdate' ,
1851+ 'ErrorBoundary render error' ,
1852+ 'ErrorBoundary componentDidUpdate' ,
1853+ ] ) ;
1854+
1855+ expect ( container . firstChild . textContent ) . toBe ( 'Caught an error: Hello.' ) ;
1856+ } ) ;
1857+
1858+ it ( 'catches errors in useLayoutEffect' , ( ) => {
1859+ const container = document . createElement ( 'div' ) ;
1860+ ReactDOM . render (
1861+ < ErrorBoundary >
1862+ < BrokenUseLayoutEffect > Initial value</ BrokenUseLayoutEffect >
1863+ </ ErrorBoundary > ,
1864+ container ,
1865+ ) ;
1866+ expect ( log ) . toEqual ( [
1867+ 'ErrorBoundary constructor' ,
1868+ 'ErrorBoundary componentWillMount' ,
1869+ 'ErrorBoundary render success' ,
1870+ 'BrokenUseLayoutEffect render' ,
1871+ 'BrokenUseLayoutEffect useLayoutEffect [!]' ,
1872+ // Fiber proceeds with the hooks
1873+ 'ErrorBoundary componentDidMount' ,
1874+ // The error propagates to the higher boundary
1875+ 'ErrorBoundary static getDerivedStateFromError' ,
1876+ // Fiber retries from the root
1877+ 'ErrorBoundary componentWillUpdate' ,
1878+ 'ErrorBoundary render error' ,
1879+ 'ErrorBoundary componentDidUpdate' ,
1880+ ] ) ;
1881+
1882+ expect ( container . firstChild . textContent ) . toBe ( 'Caught an error: Hello.' ) ;
1883+ } ) ;
1884+
17981885 it ( 'propagates errors inside boundary during componentDidMount' , ( ) => {
17991886 const container = document . createElement ( 'div' ) ;
18001887 ReactDOM . render (
0 commit comments