Web Dev

Building a Custom Rendering System in React Using Fiber Architecture

When React introduced Fiber in version 16, it revolutionized how the framework handles rendering, updates, and scheduling. The Fiber architecture is not just an implementation detail—it’s a fundamental rethinking of how React reconciles the virtual DOM and commits updates to the real DOM (or any other rendering target).

At its core, React Fiber is a reimplementation of the React reconciliation algorithm designed to make rendering incremental, interruptible, and prioritized. While most developers use React without ever needing to touch the internals, understanding Fiber enables you to extend React beyond the DOM—into custom renderers for mobile, terminal UIs, game engines, and even hardware dashboards.

In this article, we’ll explore how React Fiber works under the hood and walk through how to build a simplified custom renderer using the react-reconciler package, leveraging the same architecture that powers React DOM.


1. Why Fiber Was Needed

Before Fiber, React’s rendering was synchronous and recursive. Each update traversed the entire tree of components, recalculating virtual DOM nodes, comparing them, and then updating the real DOM. This was fine for small trees, but large UIs could become unresponsive because the JavaScript thread was monopolized during reconciliation.

Fiber solves this by introducing cooperative scheduling: the ability to split rendering work into units of work (fibers) that can be paused, resumed, or aborted. This makes React’s rendering process interruptible—allowing higher-priority updates (like user input) to preempt lower-priority rendering.


2. The Fiber Node: A Linked Data Structure

A Fiber is essentially a JavaScript object representing a unit of work for a React element. Each fiber corresponds to a component instance (class or functional) and contains references to:

  • The component’s type (function, class, host element, etc.)
  • Its props and state
  • Pointers to its child, sibling, and parent (return) fibers
  • Effect tags that describe what needs to be committed (e.g., Placement, Update, Deletion)
  • An alternate pointer for the work-in-progress version of the fiber

In essence, React maintains two fiber trees:

  • The current tree, representing the UI currently on the screen.
  • The work-in-progress tree, which React builds while processing updates.

Once reconciliation completes, React swaps these trees—making updates seamless and atomic.


3. The Two Phases of Fiber Rendering

Fiber’s rendering process occurs in two distinct phases:

a. The Render (Reconciliation) Phase

This is the diffing phase where React walks through the tree, calling component functions, reconciling child elements, and generating a new work-in-progress tree. This phase can be paused or restarted, as it’s pure computation—no DOM mutations occur yet.

b. The Commit Phase

Once the reconciliation is complete, React performs the commit phase, applying all the recorded side effects (DOM insertions, updates, deletions). This phase must run synchronously because it directly affects the UI.

[Render Phase] → [Commit Phase] (async) (sync)

This two-phase model allows React to keep the app responsive during heavy updates by yielding control between render units.


4. Building a Custom Renderer

React’s core library (react) is platform-agnostic—it doesn’t know about the DOM, Canvas, or Native UI.

The rendering logic lives in platform-specific packages like react-dom and react-native.

Using the react-reconciler package, you can implement your own renderer that tells React how to render and update elements in your custom environment.

Let’s build a simple custom renderer that outputs a UI into the console instead of the DOM—a minimal “React Console” renderer.


5. Installing Dependencies

npm install react react-reconciler

6. Creating the Renderer

Create a file consoleRenderer.js:

import Reconciler from 'react-reconciler'; const ConsoleHostConfig = { now: Date.now, supportsMutation: true, getRootHostContext(rootContainerInstance) { return null; }, getChildHostContext(parentHostContext, type, rootContainerInstance) { return null; }, prepareForCommit() { // No special prep needed return null; }, resetAfterCommit() { // After commit, nothing special to do }, createInstance(type, props, rootContainerInstance) { return { type, props }; }, appendInitialChild(parentInstance, child) { if (!parentInstance.children) parentInstance.children = []; parentInstance.children.push(child); }, finalizeInitialChildren() { return false; }, appendChildToContainer(container, child) { container.push(child); }, prepareUpdate() { return true; }, commitUpdate(instance, updatePayload, type, oldProps, newProps) { instance.props = newProps; }, removeChildFromContainer(container, child) { const index = container.indexOf(child); if (index !== -1) container.splice(index, 1); }, }; const ConsoleReconciler = Reconciler(ConsoleHostConfig); export function render(element, container) { if (!container.root) { container.root = ConsoleReconciler.createContainer(container, false, false); } ConsoleReconciler.updateContainer(element, container.root, null, () => { console.log('Rendered to console'); }); }

This HostConfig defines how React’s reconciliation algorithm interacts with your “host environment” — in this case, the console.


7. Using the Custom Renderer

Now create index.js:

import React from 'react'; import { render } from './consoleRenderer.js'; function Message({ text }) { return <line content={text} />; } function App() { return ( <screen> <Message text="Hello Fiber!" /> <Message text="Custom React Renderer 🚀" /> </screen> ); } const container = []; render(<App />, container); console.log(JSON.stringify(container, null, 2));

When you run this script with Node.js, you’ll see the reconciled output tree printed as JSON.

Although it doesn’t render pixels, it demonstrates how React Fiber performs reconciliation independent of the DOM.


8. How Fiber Manages Scheduling and Priorities

Fiber’s design enables time-slicing and concurrent rendering. Each unit of work (fiber) can yield back to the scheduler if time runs out, allowing React to handle high-priority tasks like animations or input events first.

React uses a priority-based scheduler (inspired by browser frame deadlines) where each update is assigned a “lane” (a bucket of priority). For example:

  • User input → high priority (render immediately)
  • Network updates → medium priority
  • Background rendering → low priority

This model allows React to be responsive under load, ensuring smooth frame rates even in complex UIs.


9. Comparing to React DOM

In React DOM, the HostConfig maps to actual DOM APIs:

  • createInstancedocument.createElement(type)
  • appendChildToContainerparent.appendChild(child)
  • commitUpdate → apply property diffs to DOM nodes

Your custom renderer can replace those with any environment-specific logic—e.g., draw shapes on a canvas, send messages to a terminal, or even update hardware states via serial ports.


10. Debugging and DevTools Integration

The react-reconciler package also supports DevTools integration by providing hooks like supportsMutation, getPublicInstance, and hideInstance. By implementing these correctly, you can make your custom renderer debuggable in React DevTools just like React DOM or React Native.

For production-grade renderers (like React Three Fiber or React PDF), implementing these hooks correctly ensures consistent debugging and profiling support.


11. Real-World Examples of Custom Renderers

Many successful libraries use React Fiber to render outside the browser DOM:

Renderer Target Environment Example
React Native Mobile (iOS/Android) <View> → native view
React Three Fiber WebGL/Three.js <mesh> → 3D object
React Ink Terminal UIs <Box> → console layout
React PDF PDF documents <Page> → PDF canvas

Each of these libraries defines a unique HostConfig that describes how components are created, updated, and destroyed in their respective environments—powered entirely by the same Fiber core.


12. Key Takeaways

  • Fiber is not just a diffing algorithm — it’s a scheduling system that enables concurrency and prioritization in rendering.
  • The render phase is interruptible and pure, while the commit phase is synchronous and mutates the target environment.
  • With react-reconciler, you can build your own renderer that leverages React’s component model and reconciliation logic for any target, not just the DOM.
  • Custom renderers unlock powerful possibilities—terminal dashboards, canvas-based UIs, VR interfaces, and even IoT device control panels.

13. Conclusion

React Fiber fundamentally changed the way React interacts with the rendering environment.

By splitting rendering work into small, manageable units and introducing a priority-based scheduler, React became capable of delivering concurrent, interruptible, and resilient UI updates.

Building your own custom renderer with the Fiber architecture gives you a deep appreciation for how React operates at its core—and empowers you to extend it into entirely new domains beyond the web browser.

If you’ve ever used React in a non-DOM context—React Native, React Three Fiber, or React Ink—you’ve already seen Fiber’s power in action. Now, you know how to harness that same power yourself.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button