How Fre's capture() Function Processes Fibers During Reconciliation

Fre's capture() function in src/reconcile.ts acts as the step-function of the reconciler, identifying fiber types, executing component logic or updating DOM nodes, and returning the next fiber to traverse until the cooperative scheduler yields.

The Fre library implements a modern, React-like fiber architecture for building user interfaces. At the heart of its reconciliation phase lies the capture() function, which orchestrates how each node in the fiber graph is processed, updated, and traversed during the render cycle.

What Is the capture() Function?

The capture() function serves as the primary workhorse inside Fre's reconciliation loop. Defined in [src/reconcile.ts](https://github.com/frejs/fre/blob/master/src/reconcile.ts), it receives a single fiber node and performs the necessary computations to advance the virtual tree's state. After processing the current fiber, it returns the next fiber to visit—either a child, sibling, or an ancestor's sibling—enabling the depth-first traversal of the component tree without recursion.

Step-by-Step Fiber Processing

When capture() processes a fiber, it follows a strict sequence of type checks and update routines. Each step determines how the reconciler treats the current node.

1. Fiber Type Identification

First, capture() distinguishes between function components and host elements by checking the fiber's type property. It sets the isComp flag based on whether the type is a function.

fiber.isComp = isFn(fiber.type)               // src/reconcile.ts#L86‑L88

This classification determines which update path the reconciler follows for the remainder of the function.

2. Short-Circuiting Memoized Components

If the fiber represents a memoized component whose props have not changed, capture() immediately bails out to avoid unnecessary work. It resets the memo flag and jumps to the next sibling, skipping the entire subtree.

if (isMemo(fiber)) {
  fiber.memo = false
  return sibling(fiber)                       // src/reconcile.ts#L90‑L93
}

This optimization prevents redundant re-renders of pure components.

3. Executing Function Components

For standard function components, capture() invokes updateHook(), which prepares the hook cursor, creates a fragment node, executes the component function, and reconciles the returned children.

const isMatchSuspenseOrErrorBoundary = updateHook(fiber)   // src/reconcile.ts#L94‑L96
if (isMatchSuspenseOrErrorBoundary) return isMatchSuspenseOrErrorBoundary

If the component throws a Promise, suspenseRender() creates a Suspense boundary; if it throws an error, errorBoundaryRender() establishes an ErrorBoundary. These returns exit the current capture cycle to handle the boundary state.

4. Handling Host Elements

When capture() encounters a non-component fiber—such as a DOM element—it delegates to updateHost(). This function creates or updates the real DOM node and then reconciles the fiber's children.

updateHost(fiber as FiberHost)               // src/reconcile.ts#L98‑L100

Host processing bridges the virtual representation with the actual browser DOM.

5. Advancing the Traversal

Finally, capture() determines the next fiber to process. It returns the first child to dive deeper into the tree, or if no child exists, it calls sibling() to find the next sibling or an ancestor's sibling.

return fiber.child || sibling(fiber)          // src/reconcile.ts#L101‑L102

The sibling() function walks up the tree until it locates the next valid node or reaches the root, effectively implementing the depth-first traversal pattern.

The Cooperative Scheduling Loop

The capture() function operates within a controlled loop inside reconcile(), which respects the browser's scheduling constraints. The surrounding code repeatedly invokes capture() until the work is complete or the scheduler signals a yield.

while (fiber && !shouldYield()) {
  fiber = capture(fiber) as any
}

This integration with shouldYield() from src/schedule.ts enables Fre to implement cooperative scheduling, allowing the renderer to pause and resume work to maintain frame rates and responsiveness.

Practical Code Examples

Basic Component Rendering

When you mount a component, capture() processes the function component first, then the resulting host elements.

import { render } from 'fre'
import { h } from 'fre/h'

function Counter({ start }) {
  const [count, setCount] = useState(start)
  return h('button', { onClick: () => setCount(c => c + 1) }, count)
}

const container = document.getElementById('root')
render(h(Counter, { start: 0 }), container)

The render() call initiates reconciliation, triggering capture() to process the Counter fiber, execute its hook logic, and then traverse the button host element.

Manual Component Updates

You can trigger reconciliation manually by updating props and calling update().

// Assuming rootFiber is the fiber returned from render()
rootFiber.props.children = h(Counter, { start: 10 })
update(rootFiber)

This marks the fiber dirty, causing capture() to re-execute the component with the new start value and diff the resulting subtree.

Suspense Boundary Handling

When a component throws a Promise during execution, capture() intercepts it to establish a Suspense boundary.

function AsyncComp() {
  const data = fetchData()          // Returns a Promise
  return h('div', null, data)       // Throws the Promise
}

render(
  h(Suspense, { fallback: h('span', null, 'Loading') },
    h(AsyncComp, null)
  ),
  container
)

During capture(), when AsyncComp throws the Promise, the function calls suspenseRender() to swap the primary subtree with the fallback UI until the Promise resolves.

Key Source Files Involved

Several modules collaborate to support the capture() workflow:

  • src/reconcile.ts – Implements capture(), sibling(), and the diffing algorithm that drives the reconciliation process.
  • src/type.ts – Defines the Fiber interface and TAG constants used to distinguish node types during traversal.
  • src/schedule.ts – Provides schedule() and shouldYield() for cooperative scheduling around the capture() loop.
  • src/commit.ts – Performs DOM mutations after capture() marks fibers dirty and work completes.
  • src/h.ts – Supplies type-checking utilities like isMemo, isFn, and JSX element creation helpers.

Summary

  • capture() in src/reconcile.ts is the central step-function that processes each fiber during Fre's reconciliation phase.
  • It identifies fiber types using isFn() to route components through updateHook() and host elements through updateHost().
  • Memoized components are short-circuited early by returning sibling(fiber) when props haven't changed.
  • The function handles error boundaries and Suspense by catching thrown Promises or errors during updateHook().
  • Traversal advances via fiber.child or sibling(), enabling depth-first walks while integrated with cooperative scheduling via shouldYield().

Frequently Asked Questions

Where is the capture() function defined in Fre?

The capture() function is defined in src/reconcile.ts. It serves as the core work unit processor during the reconciliation loop, handling type checking, component execution, and traversal logic for the fiber graph.

How does capture() handle Suspense boundaries?

When a function component throws a Promise during execution inside updateHook(), capture() catches this and returns the result of suspenseRender(). This creates a Suspense boundary that renders a fallback UI until the asynchronous operation completes, effectively pausing the subtree's rendering.

What happens when capture() encounters a memoized component?

If isMemo(fiber) returns true and the props haven't changed, capture() resets the memo flag and immediately returns sibling(fiber). This skips the entire subtree reconciliation, preventing unnecessary recomputation of pure components.

How does capture() decide which fiber to process next?

After processing the current fiber, capture() returns fiber.child if a child exists to continue depth-first traversal. If no child exists, it calls sibling(fiber) to find the next sibling or walk up the tree to find an ancestor's sibling, ensuring complete tree coverage without recursion.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →