@ -1,4 +1,4 @@
import { getIntl } from '@umijs/max' ;
import { getIntl , useModel } from '@umijs/max' ;
import { Button , Result } from 'antd' ;
import React from 'react' ;
@ -10,42 +10,118 @@ function isChunkLoadError(error: Error): boolean {
) ;
}
function getSubTitleId ( isChunkError : boolean , isOffline : boolean ) : string {
if ( ! isChunkError ) return 'app.error.render.description' ;
return isOffline
? 'app.error.chunk.description.offline'
: 'app.error.chunk.description.online' ;
}
function renderErrorFallback (
error : Error ,
isOnline : boolean ,
onRetry : ( ) = > void ,
) {
const intl = getIntl ( ) ;
const isOffline = ! isOnline ;
const isChunkError = isChunkLoadError ( error ) ;
return (
< Result
status = "error"
title = { intl . formatMessage ( {
id : isChunkError ? 'app.error.chunk.title' : 'app.error.render.title' ,
defaultMessage : isChunkError
? 'Failed to load page'
: 'Something went wrong' ,
} ) }
subTitle = { intl . formatMessage ( {
id : getSubTitleId ( isChunkError , isOffline ) ,
defaultMessage :
isChunkError && isOffline
? 'Your network connection has been lost. Please check your connection and refresh.'
: isChunkError
? 'Page resources failed to load. Please refresh and try again.'
: 'Sorry, an error occurred on this page. Please refresh or go back to the home page.' ,
} ) }
extra = { [
< Button type = "primary" key = "retry" onClick = { onRetry } >
{ intl . formatMessage ( {
id : 'app.error.retry' ,
defaultMessage : 'Refresh' ,
} ) }
< / Button > ,
< Button href = "/" key = "home" >
{ intl . formatMessage ( {
id : 'app.error.home' ,
defaultMessage : 'Back Home' ,
} ) }
< / Button > ,
] }
/ >
) ;
}
interface ErrorBoundaryProps {
children : React.ReactNode ;
/** When provided, skips internal online/offline listeners and uses this value. */
isOnline? : boolean ;
}
interface ErrorBoundaryState {
hasError : boolean ;
error : Error | null ;
isOnline : boolean ;
}
export default class ErrorBoundary extends React . Component <
{ children : React.ReactNode } ,
/ * *
* Class - based error boundary with offline - aware error messages .
* Accepts optional ` isOnline ` prop ; falls back to own navigator . onLine tracking when unset .
* /
export class ErrorBoundaryClass extends React . Component <
ErrorBoundaryProps ,
ErrorBoundaryState
> {
state : ErrorBoundaryState = {
hasError : false ,
error : null ,
isOnline : typeof navigator !== 'undefined' ? navigator.onLine : true ,
} ;
state : ErrorBoundaryState = { hasError : false , error : null } ;
private ownOnline =
typeof navigator !== 'undefined' ? navigator.onLine : true ;
static getDerivedStateFromError ( error : Error ) : Partial < ErrorBoundaryState > {
return { hasError : true , error } ;
}
componentDidMount() {
window . addEventListener ( 'online' , this . handleOnline ) ;
window . addEventListener ( 'offline' , this . handleOffline ) ;
if ( this . props . isOnline === undefined ) {
window . addEventListener ( 'online' , this . handleOnline ) ;
window . addEventListener ( 'offline' , this . handleOffline ) ;
}
}
componentDidUpdate ( prev : ErrorBoundaryProps ) {
// Auto-reload on recovery when isOnline prop transitions to true
if (
this . props . isOnline === true &&
prev . isOnline === false &&
this . state . hasError
) {
window . location . reload ( ) ;
}
}
componentWillUnmount() {
window . removeEventListener ( 'online' , this . handleOnline ) ;
window . removeEventListener ( 'offline' , this . handleOffline ) ;
if ( this . props . isOnline === undefined ) {
window . removeEventListener ( 'online' , this . handleOnline ) ;
window . removeEventListener ( 'offline' , this . handleOffline ) ;
}
}
handleOnline = ( ) = > {
this . setState ( { isOnline : true } ) ;
this . ownOnline = true ;
if ( this . state . hasError ) window . location . reload ( ) ;
} ;
handleOffline = ( ) = > this . setState ( { isOnline : false } ) ;
handleOffline = ( ) = > {
this . ownOnline = false ;
} ;
componentDidCatch ( error : Error , info : React.ErrorInfo ) {
console . error ( '[ErrorBoundary]' , error , info . componentStack ) ;
@ -59,55 +135,29 @@ export default class ErrorBoundary extends React.Component<
}
} ;
render() {
if ( ! this . state . hasError || ! this . state . error ) {
return this . props . children ;
}
getIsOnline ( ) : boolean {
return this . props . isOnline ? ? this . ownOnline ;
}
const { error } = this . state ;
const intl = getIntl ( ) ;
const isOffline = ! this . state . isOnline ;
const isChunkError = isChunkLoadError ( error ) ;
const subTitleId = isChunkError
? isOffline
? 'app.error.chunk.description.offline'
: 'app.error.chunk.description.online'
: 'app.error.render.description' ;
return (
< Result
status = "error"
title = { intl . formatMessage ( {
id : isChunkError ? 'app.error.chunk.title' : 'app.error.render.title' ,
defaultMessage : isChunkError
? 'Failed to load page'
: 'Something went wrong' ,
} ) }
subTitle = { intl . formatMessage ( {
id : subTitleId ,
defaultMessage :
isChunkError && isOffline
? 'Your network connection has been lost. Please check your connection and refresh.'
: isChunkError
? 'Page resources failed to load. Please refresh and try again.'
: 'Sorry, an error occurred on this page. Please refresh or go back to the home page.' ,
} ) }
extra = { [
< Button type = "primary" key = "retry" onClick = { this . handleRetry } >
{ intl . formatMessage ( {
id : 'app.error.retry' ,
defaultMessage : 'Refresh' ,
} ) }
< / Button > ,
< Button href = "/" key = "home" >
{ intl . formatMessage ( {
id : 'app.error.home' ,
defaultMessage : 'Back Home' ,
} ) }
< / Button > ,
] }
/ >
render() {
if ( ! this . state . hasError || ! this . state . error ) return this . props . children ;
return renderErrorFallback (
this . state . error ,
this . getIsOnline ( ) ,
this . handleRetry ,
) ;
}
}
/** Functional wrapper providing network state via useModel('network'). */
const ErrorBoundary : React.FC < { children : React.ReactNode } > = ( {
children ,
} ) = > {
const { isOnline } = useModel ( 'network' ) ;
return (
< ErrorBoundaryClass isOnline = { isOnline } > { children } < / ErrorBoundaryClass >
) ;
} ;
export default ErrorBoundary ;