# How Fre's ErrorBoundary Catches and Handles Rendering Errors

> Discover how Fre's ErrorBoundary uses a try catch mechanism to find and recover from rendering errors smoothly, ensuring a better user experience.

- Repository: [frejs/fre](https://github.com/frejs/fre)
- Tags: internals
- Published: 2026-03-02

---

**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`](https://github.com/frejs/fre/blob/main/src/reconcile.ts).

## The ErrorBoundary Component Marker

In [`src/h.ts`](https://github.com/frejs/fre/blob/main/src/h.ts) at lines 84–86, the `ErrorBoundary` component is defined as a simple functional component that does nothing but return its children:

```typescript
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`](https://github.com/frejs/fre/blob/main/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:

```typescript
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`](https://github.com/frejs/fre/blob/main/src/reconcile.ts), the reconciler differentiates between errors that should trigger an ErrorBoundary and Promises that indicate a suspended component (for Suspense):

```typescript
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`](https://github.com/frejs/fre/blob/main/src/reconcile.ts)) to locate the nearest ancestor that can handle the error:

```typescript
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`](https://github.com/frejs/fre/blob/main/src/reconcile.ts):

```typescript
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`](https://github.com/frejs/fre/blob/main/demo/src/error-boundary.tsx) demonstrates both function-based and element-based fallbacks:

```tsx
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.ts`](https://github.com/frejs/fre/blob/main/src/h.ts) that enables the reconciler to identify error interception points in the fiber tree.
- **Error catching** happens in [`src/reconcile.ts`](https://github.com/frejs/fre/blob/main/src/reconcile.ts) inside `updateHook` (lines 61–69), which wraps component execution in a `try…catch` block.
- **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 the `fiber.parent` chain until finding a fiber with `type === 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`](https://github.com/frejs/fre/blob/main/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`](https://github.com/frejs/fre/blob/main/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`](https://github.com/frejs/fre/blob/main/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`](https://github.com/frejs/fre/blob/main/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.