ErrorBoundary
<ErrorBoundary> wraps a subtree and shows fallback content if mounting
that subtree fails. It keeps one broken widget from taking down the page.
<error-boundary> is the same component.
<ErrorBoundary fallback="<p>Something went wrong</p>">
<RiskyWidget />
</ErrorBoundary>
If mounting RiskyWidget fails, the boundary writes the fallback
into its container and reports the error to the global handler.
What Reaches the Boundary
A boundary sees one thing: a failure to mount its own children.
Anything that throws while a child is initialising rejects that child's mount, and the nearest enclosing boundary shows its fallback. Errors after that point do not.
| Where the error happens | Fallback renders? | Reported to the error handler? |
|---|---|---|
A component <script> body throws |
Yes | Yes |
An onMount() callback throws |
Yes | Yes |
An effect() throws on its first run, during mount |
Yes | Yes |
An effect() throws on a later re-run |
No | Yes |
| A template expression throws | No | Partially |
| An event handler throws | No | No - use try/catch |
| A promise rejects after mount | No | No - use try/catch |
The two effect() rows are the pair to remember: the first run happens inside
the mount, so the boundary sees it; a re-run happens later, where one failing effect is
caught on its own so it cannot stop the others.
For everything in the "No" rows, catch it yourself and call handleError() so
it still reaches your reporting:
<script>
import { handleError } from 'jafjs'
async function load() {
try {
const res = await fetch('/api/report')
if (!res.ok) throw new Error('Failed: ' + res.status)
data = await res.json()
} catch (e) {
handleError(e, { component: 'Report' })
}
}
</script>
The fallback Prop
fallback is a string of HTML, written straight into the container. It defaults
to <p>Something went wrong</p>.
Keep the fallback to plain markup. A component tag in it, or an expression, is a build error naming the fallback: the compiler never walked that markup, so there is nothing behind the tag and nothing to interpolate. The same holds for a Show, Switch or Suspense fallback. A For fallback is the one exception in the family - its expressions are compiled.
Nesting and Isolation
The innermost boundary wins and siblings are unaffected. That is what makes one boundary per independent panel the pattern worth reaching for:
<div class="dashboard">
<ErrorBoundary fallback="Chart unavailable">
<RevenueChart />
</ErrorBoundary>
<ErrorBoundary fallback="Stats unavailable">
<StatsPanel />
</ErrorBoundary>
</div>
One boundary around a <slot /> in a layout does the same job for a whole
page.
There Is No Retry
A boundary latches. Once it has shown its fallback it stays there for the life of that
mount: it does not re-attempt the subtree, and there is no reset() or
onError prop.
If you want to offer a retry, say so in the fallback text.
Reporting Errors Globally
Every error a boundary catches is also passed to the global handler, along with the ones it does not catch.
Register one for the whole app:
<script>
import { setErrorHandler } from 'jafjs'
setErrorHandler((error, info) => {
console.error('Caught:', error.message, info.component)
})
</script>
setErrorHandler and handleError are not auto-available - import
them from jafjs.
Only one handler is active at a time.
In development the error overlay appears as well as the handler; press Esc to
close it.
Related
- Error Boundaries - the longer guide, including the dev overlay
- Error Pages - whole-page 404 and 403 handling