Error Boundaries
An error boundary wraps part of your page. If something inside it throws while mounting, the boundary shows a fallback instead and the rest of the page carries on.
<error-boundary fallback="<p>Something went wrong</p>">
<BuggyComponent />
</error-boundary>
fallback is a literal string of HTML written straight into the
boundary. The compiler never walks it, so an expression in it, or a component
tag - fallback="<ErrorPage />" - is a build error naming the
fallback. Keep it to plain markup.
What it catches
"Caught" means the fallback renders.
Several rows below are still reported to setErrorHandler and the
dev overlay even when the fallback does not appear.
| Where the error is thrown | Fallback renders? |
|---|---|
A component <script> body |
Yes |
An onMount() callback |
Yes |
An effect(), on its first run during mount |
Yes |
An effect(), on a later re-run |
No - caught per effect and reported |
| A template expression | No - reported partially |
| An event handler | No - use try/catch |
| An async operation | No - use try/catch |
The first three rows are one rule: anything that throws while a component is initialising rejects that component's mount, and the nearest enclosing boundary takes over.
An effect's later re-runs happen in the scheduler instead, which catches per effect so one bad effect cannot stop the others.
With no boundary above it, the same throw is reported to the error handler and the dev overlay, and the rest of the page still renders.
A boundary changes where an error goes, not whether it is survivable.
The helper reference carries the same table with the reporting column.
Nesting
The innermost boundary wins, so failures stay local:
<error-boundary fallback="<p>Outer fallback</p>">
<error-boundary fallback="<p>Inner fallback</p>">
<RiskyWidget />
</error-boundary>
<SafeWidget /> <!-- Still renders if RiskyWidget fails -->
</error-boundary>
That is the useful shape for a dashboard: one boundary per widget, so a broken chart costs you the chart and nothing else.
<div class="dashboard">
<error-boundary fallback="<p>Chart unavailable</p>">
<Chart data={chartData} />
</error-boundary>
<error-boundary fallback="<p>Stats unavailable</p>">
<Stats data={statsData} />
</error-boundary>
</div>
Reporting errors
setErrorHandler() receives every error JAF catches, boundary or
not. It and handleError() are the two error functions you have to
import:
import { setErrorHandler } from 'jafjs'
setErrorHandler((error, info) => {
console.error('Caught:', error.message)
console.log('Component:', info.component)
// Send to error tracking
analytics.trackError(error, info)
})
The handler is (error, info) => void, where info
carries an optional component name.
There is one handler at a time; setting a new one replaces it.
Event handlers and async work are yours to catch. Pass what you catch to
handleError() and it joins the same pipeline:
<script>
import { handleError } from 'jafjs'
async function loadData() {
try {
const res = await fetch('/api/data')
data = await res.json()
} catch (error) {
handleError(error, { component: 'DataLoader' })
}
}
</script>
In development every error also raises an overlay with
the message, the stack and the component name where one is known. Press
Esc to dismiss it.