Browse Source

fix: make ErrorBoundary reactive to network state changes

- Move isOnline into component state so error fallback UI updates
  when network status changes (online/offline events trigger re-render)
- Tighten isChunkLoadError regex: replace broad /imported module/i
  with specific /Failed to fetch dynamically imported module/i to
  avoid false positives on unrelated runtime errors

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pull/11756/head
afc163 5 months ago
parent
commit
57d6be0267
  1. 31
      src/components/ErrorBoundary/index.tsx

31
src/components/ErrorBoundary/index.tsx

@ -6,7 +6,7 @@ function isChunkLoadError(error: Error): boolean {
return ( return (
error.name === 'ChunkLoadError' || error.name === 'ChunkLoadError' ||
/(?:loading|failed to load) (?:css )?chunk/i.test(error.message) || /(?:loading|failed to load) (?:css )?chunk/i.test(error.message) ||
/imported module/i.test(error.message) /Failed to fetch dynamically imported module/i.test(error.message)
); );
} }
@ -17,9 +17,13 @@ function getSubTitleId(isChunkError: boolean, isOffline: boolean): string {
: 'app.error.chunk.description.online'; : 'app.error.chunk.description.online';
} }
function renderErrorFallback(error: Error, onReload: () => void) { function renderErrorFallback(
error: Error,
isOnline: boolean,
onReload: () => void,
) {
const intl = getIntl(); const intl = getIntl();
const isOffline = !navigator.onLine; const isOffline = !isOnline;
const isChunkError = isChunkLoadError(error); const isChunkError = isChunkLoadError(error);
return ( return (
@ -71,13 +75,18 @@ interface ErrorBoundaryProps {
interface ErrorBoundaryState { interface ErrorBoundaryState {
hasError: boolean; hasError: boolean;
error: Error | null; error: Error | null;
isOnline: boolean;
} }
export default class ErrorBoundary extends React.Component< export default class ErrorBoundary extends React.Component<
ErrorBoundaryProps, ErrorBoundaryProps,
ErrorBoundaryState ErrorBoundaryState
> { > {
state: ErrorBoundaryState = { hasError: false, error: null }; state: ErrorBoundaryState = {
hasError: false,
error: null,
isOnline: typeof navigator !== 'undefined' ? navigator.onLine : true,
};
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> { static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
return { hasError: true, error }; return { hasError: true, error };
@ -85,14 +94,16 @@ export default class ErrorBoundary extends React.Component<
componentDidMount() { componentDidMount() {
window.addEventListener('online', this.handleOnline); window.addEventListener('online', this.handleOnline);
window.addEventListener('offline', this.handleOffline);
} }
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener('online', this.handleOnline); window.removeEventListener('online', this.handleOnline);
window.removeEventListener('offline', this.handleOffline);
} }
/** Auto-reload when coming back online, but only for chunk load errors. */
handleOnline = () => { handleOnline = () => {
this.setState({ isOnline: true });
if ( if (
this.state.hasError && this.state.hasError &&
this.state.error && this.state.error &&
@ -102,6 +113,10 @@ export default class ErrorBoundary extends React.Component<
} }
}; };
handleOffline = () => {
this.setState({ isOnline: false });
};
componentDidCatch(error: Error, info: React.ErrorInfo) { componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('[ErrorBoundary]', error, info.componentStack); console.error('[ErrorBoundary]', error, info.componentStack);
} }
@ -112,6 +127,10 @@ export default class ErrorBoundary extends React.Component<
render() { render() {
if (!this.state.hasError || !this.state.error) return this.props.children; if (!this.state.hasError || !this.state.error) return this.props.children;
return renderErrorFallback(this.state.error, this.handleReload); return renderErrorFallback(
this.state.error,
this.state.isOnline,
this.handleReload,
);
} }
} }

Loading…
Cancel
Save