Skip to content

ErrorBoundary

ErrorBoundary catches errors thrown during rendering or in effects, and displays a fallback UI instead of crashing the whole app.

Without one, a single thrown error bubbles up through the fiber tree and kills the process. With one, the error is contained to the subtree it wraps.

Basic usage

·CODE
import { ErrorBoundary } from '@termuijs/jsx'
import { Box, Text } from '@termuijs/widgets'

function App() {
    return (
        <ErrorBoundary
            fallback={(err) => (
                <Box border="single" borderColor={{ type: 'named', name: 'red' }} padding={1}>
                    <Text bold color={{ type: 'named', name: 'red' }}>Something went wrong</Text>
                    <Text>{err.message}</Text>
                </Box>
            )}
        >
            <Dashboard />
        </ErrorBoundary>
    )
}

Props

PropTypeRequiredDescription
fallback(err: Error) => VNodeYesFunction that returns the UI to show when an error occurs
onError(err: Error) => voidNoCalled when an error is caught, use for logging
childrenVNodeYesThe component tree to protect

Logging errors

·CODE
<ErrorBoundary
    onError={(err) => {
        writeLog(`[ERROR] ${err.message}\n${err.stack}`)
    }}
    fallback={(err) => <Text>Error: {err.message}</Text>}
>
    <RiskyWidget />
</ErrorBoundary>

Placement strategy

One ErrorBoundary at the app root catches everything, but you lose the ability to recover partially. The recommended pattern is two tiers:

Root boundary, prevents total app crash, shows a global error screen:

·CODE
function Root() {
    return (
        <ErrorBoundary fallback={GlobalErrorScreen}>
            <App />
        </ErrorBoundary>
    )
}

Section boundaries, contain errors to one panel while the rest of the app keeps running:

·CODE
function Dashboard() {
    return (
        <col>
            <ErrorBoundary fallback={(e) => <Text>Chart failed: {e.message}</Text>}>
                <ChartPanel />
            </ErrorBoundary>
            <ErrorBoundary fallback={(e) => <Text>Logs failed: {e.message}</Text>}>
                <LogPanel />
            </ErrorBoundary>
        </col>
    )
}

If ChartPanel crashes, LogPanel keeps rendering.

What gets caught

ScenarioCaught?
Error thrown during initial renderYes
Error thrown in useEffect callbackYes
Error thrown inside an event handler (useInput, useKeymap)No, these run outside the fiber render cycle
Unhandled promise rejection in an async actionNo, use try/catch in async code

For event handler errors, wrap the handler body in a try/catch and call useNotifications or similar to surface the error to the user.

See also