@@ -28,16 +28,20 @@ describe('ReactErrorBoundaries', () => {
28
28
let BrokenComponentWillMountErrorBoundary ;
29
29
let BrokenComponentDidMountErrorBoundary ;
30
30
let BrokenRender ;
31
+ let BrokenUseEffect ;
32
+ let BrokenUseLayoutEffect ;
31
33
let ErrorBoundary ;
32
34
let ErrorMessage ;
33
35
let NoopErrorBoundary ;
34
36
let RetryErrorBoundary ;
35
37
let Normal ;
36
38
37
39
beforeEach ( ( ) => {
40
+ jest . useFakeTimers ( ) ;
38
41
jest . resetModules ( ) ;
39
42
PropTypes = require ( 'prop-types' ) ;
40
43
ReactFeatureFlags = require ( 'shared/ReactFeatureFlags' ) ;
44
+ ReactFeatureFlags . enableHooks = true ;
41
45
ReactFeatureFlags . replayFailedUnitOfWorkWithInvokeGuardedCallback = false ;
42
46
ReactDOM = require ( 'react-dom' ) ;
43
47
React = require ( 'react' ) ;
@@ -386,6 +390,28 @@ describe('ReactErrorBoundaries', () => {
386
390
}
387
391
} ;
388
392
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
+
389
415
NoopErrorBoundary = class extends React . Component {
390
416
constructor ( props ) {
391
417
super ( props ) ;
@@ -1795,6 +1821,67 @@ describe('ReactErrorBoundaries', () => {
1795
1821
expect ( log ) . toEqual ( [ 'ErrorBoundary componentWillUnmount' ] ) ;
1796
1822
} ) ;
1797
1823
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
+
1798
1885
it ( 'propagates errors inside boundary during componentDidMount' , ( ) => {
1799
1886
const container = document . createElement ( 'div' ) ;
1800
1887
ReactDOM . render (
0 commit comments