Implementing React Error Boundaries with user-friendly fallbacks, retry mechanisms, and error reporting.
SpaceZero includes a production-ready Error Boundary component.
// components/infra/error-boundary.tsx
export class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ hasError: boolean; error: Error | null }
> {
constructor(props: { children: React.ReactNode }) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('[SpaceZero ErrorBoundary]:', error, errorInfo);
}
handleReset = () => this.setState({ hasError: false, error: null });
handleReload = () => window.location.reload();
render() {
if (this.state.hasError) {
return (
<Modal>
<ModalBackdrop />
<ModalDialog>
<ModalHeader>
<ModalClose onClick={this.handleReset}>
<Icon name="X" />
</ModalClose>
<ModalIcon>
<Icon name="AlertTriangle" />
</ModalIcon>
<ModalTitle>Something went wrong</ModalTitle>
</ModalHeader>
<ModalDescription>
An unexpected error occurred. You can try to reload the page.
</ModalDescription>
<ModalBody>
{this.state.error && (
<Notice variant="destructive">{this.state.error.message}</Notice>
)}
</ModalBody>
<ModalFooter className="flex gap-3">
<Button variant="default" onClick={this.handleReset}>
Try Again
</Button>
<Button variant="action" onClick={this.handleReload}>
Reload
</Button>
</ModalFooter>
</ModalDialog>
</Modal>
);
}
return this.props.children;
}
}
The fallback is a modal, so it works at any nesting depth without needing page-level layout.
Wrap your app root:
// App.tsx
<ErrorBoundary>
<ToastProvider>{page}</ToastProvider>
</ErrorBoundary>
Resets error state, re-renders children:
handleReset = () => this.setState({ hasError: false, error: null });
Useful for transient errors (network blip, race condition).
Full page reload:
handleReload = () => window.location.reload();
For persistent errors requiring fresh state.
Shows the error message in a destructive Notice:
<Notice variant="destructive">{this.state.error.message}</Notice>
componentDidCatch(error, errorInfo) {
console.error('[SpaceZero ErrorBoundary]:', error, errorInfo);
// Send to Sentry, LogRocket, etc.
}
Wrap different sections independently:
<ErrorBoundary>
<Header />
</ErrorBoundary>
<ErrorBoundary>
<PageMain>
<PageContent />
</PageMain>
</ErrorBoundary>
<ErrorBoundary>
<Footer />
</ErrorBoundary>
Use same components (Card, Button) for consistency.
Always log to console/error tracking.
At minimum: "Try Again" and "Reload".
// For non-fatal errors, use Toast instead
const { toast } = useToast();
toast.destructive('Failed to save', 'Please try again');
throw new Error('Test')