Show a crash outside a page instead of a white screen

Moving between pages sometimes left a white screen until a reload: an
error outside the page's own error boundary (the layout, the sign-in
gate) unmounted the whole app. An error boundary now wraps the whole
app, shows the error's name and message, and tries again when the route
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iqbal
2026-10-30 10:20:22 +07:00
parent 977a8fafc4
commit 4e1bee4503
3 changed files with 20 additions and 6 deletions

View File

@@ -3,10 +3,15 @@ import { t } from '../i18n'
import { Empty } from './Page'
// Without this, one component throwing while rendering unmounts the whole app and leaves a white
// page. The layout keys it by route, so moving to another page clears the error.
export class ErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
// page. The layout keys it by route, so moving to another page clears the error; main.tsx also puts
// one around the whole app (the layout included), cleared by a change of `resetKey`.
export class ErrorBoundary extends Component<{ children: ReactNode; resetKey?: string; className?: string }, { error: Error | null }> {
state = { error: null as Error | null }
componentDidUpdate(prev: { resetKey?: string }) {
if (this.state.error && prev.resetKey !== this.props.resetKey) this.setState({ error: null })
}
static getDerivedStateFromError(error: Error) {
return { error }
}
@@ -19,11 +24,11 @@ export class ErrorBoundary extends Component<{ children: ReactNode }, { error: E
const { error } = this.state
if (!error) return this.props.children
return (
<div className="card">
<div className={this.props.className ?? 'card'}>
<Empty icon="info" title={t('err.crashTitle')}>
{t('err.crashCopy')}
<br />
<small>{error.message}</small>
<small>{error.name}: {error.message}</small>
</Empty>
<div style={{ display: 'flex', justifyContent: 'center', gap: 8, paddingBottom: 24 }}>
<button className="btn small" onClick={() => this.setState({ error: null })}>{t('common.retry')}</button>