How Fre's ErrorBoundary Catches and Handles Rendering Errors
Fre's ErrorBoundary catches rendering errors through a try…catch mechanism in the reconciler's updateHook, which walks up the fiber tree to find the nearest boundary and renders its fallback prop to recover gracefully.
Fre is a lightweight JavaScript library for building user interfaces that implements React-like error boundaries with minimal overhead. In the frejs/fre repository, the ErrorBoundary component acts as a declarative marker in the fiber tree, while the actual error interception and recovery logic resides entirely within the reconciliation engine in src/reconcile.ts.
The ErrorBoundary Component Marker
In src/h.ts at lines 84–86, the ErrorBoundary component is defined as a simple functional component that does nothing but return its children:
export function ErrorBoundary(props) {
return props.children
}
This component serves as a lightweight marker in the fiber tree. It does not contain state or logic; instead, the reconciler recognizes fibers whose type property equals the ErrorBoundary function and treats them as interception points for errors thrown by descendant components.
Error Detection in the Reconciler
The updateHook Try-Catch Block
The core error detection occurs in src/reconcile.ts within the updateHook function (lines 61–69). When the reconciler invokes a functional component to render it, the call is wrapped in a try…catch block:
try {
// Invoke the functional component
(fiber.type as FC)(fiber.props)
} catch (e) {
// Handle caught values
}
Any value thrown during component execution—whether an Error object or another value—is caught at this point before it can crash the entire application.
Distinguishing Errors from Suspended Promises
At lines 64–68 in src/reconcile.ts, the reconciler differentiates between errors that should trigger an ErrorBoundary and Promises that indicate a suspended component (for Suspense):
if (e instanceof Promise) {
// Route to Suspense handling
} else {
return errorBoundaryRender(fiber, e).child
}
If the caught value is not a Promise, it is routed to errorBoundaryRender for ErrorBoundary processing. This separation ensures that asynchronous data fetching and runtime errors follow distinct recovery paths.
Walking the Fiber Tree to Find the Boundary
The getBoundary Function
When an error is caught, errorBoundaryRender calls getBoundary (defined at lines 46–52 in src/reconcile.ts) to locate the nearest ancestor that can handle the error:
function getBoundary(fiber, type) {
while (fiber) {
if (fiber.type === type) return fiber
fiber = fiber.parent
}
}
This function traverses upward through the fiber.parent chain until it finds a fiber whose type matches the ErrorBoundary component. If no boundary is found in the ancestor chain, the function returns undefined.
Rendering the Fallback UI
Once a boundary is located, the reconciler renders the fallback UI at lines 55–60 in src/reconcile.ts:
if (!boundary) throw error
reconcileChildren(
boundary,
isFn(boundary.props.fallback)
? boundary.props.fallback({ error })
: simpleVnode(boundary.props.fallback)
)
If the fallback prop is a function, it is invoked with an object containing the { error }, allowing the fallback to display error details. If the fallback is a JSX element, it is reconciled directly as the new children of the boundary fiber, replacing the crashed subtree. If no boundary exists in the tree, the original error is re-thrown at line 55, aborting the render.
Practical Implementation Example
The following example from demo/src/error-boundary.tsx demonstrates both function-based and element-based fallbacks:
import { render, ErrorBoundary, h } from '../../src/index'
function Fallback({ error }) {
// Receives the caught error automatically
return <div>Oops: {error.message}</div>
}
function Bad() {
throw new Error('render error test')
}
function App() {
return (
<div>
{/* Function fallback receives error object */}
<ErrorBoundary fallback={Fallback}>
<Bad />
</ErrorBoundary>
{/* Static JSX fallback */}
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<Bad />
</ErrorBoundary>
</div>
)
}
render(<App />, document.getElementById('app'))
When Bad throws during rendering, the first ErrorBoundary renders the Fallback component with the error message, while the second renders a static div. The rest of the application continues rendering normally, isolated from the failed subtree.
Summary
- Fre's ErrorBoundary is a passive marker component defined in
src/h.tsthat enables the reconciler to identify error interception points in the fiber tree. - Error catching happens in
src/reconcile.tsinsideupdateHook(lines 61–69), which wraps component execution in atry…catchblock. - Error routing distinguishes Promises (for Suspense) from other errors at lines 64–68, sending the latter to
errorBoundaryRender. - Boundary location uses
getBoundary(lines 46–52) to walk up thefiber.parentchain until finding a fiber withtype === ErrorBoundary. - Fallback rendering (lines 55–60) supports both function props that receive
{ error }and static JSX elements, replacing the crashed component subtree while allowing the rest of the app to render.
Frequently Asked Questions
Where is the ErrorBoundary component defined in the Fre source code?
The ErrorBoundary component is defined in src/h.ts at lines 84–86 as a simple functional component that returns props.children. It serves as a marker in the fiber tree rather than containing logic, with the actual error handling implemented in the reconciler.
How does Fre distinguish between Suspense promises and ErrorBoundary errors?
In src/reconcile.ts at lines 64–68, the reconciler checks if (e instanceof Promise). Promises are routed to suspenseRender for Suspense handling, while all other caught values are passed to errorBoundaryRender to trigger error boundary recovery.
What happens if a rendering error occurs outside of an ErrorBoundary?
If getBoundary cannot find an ancestor fiber with type === ErrorBoundary, the code at line 55 in src/reconcile.ts executes if (!boundary) throw error, re-throwing the original error and aborting the render process entirely.
Can the ErrorBoundary fallback prop access the caught error object?
Yes. According to the implementation at lines 55–60 in src/reconcile.ts, if the fallback prop is a function, it is called with { error } as its argument, allowing the fallback UI to display error details. If the fallback is a JSX element, it renders statically without error information.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →