Skip to content

Back to Blog Listing

One Reconciler, Many Renderers

Part 4 of the React Internals series. React's reconciler produces host operations, and renderers such as react-dom, React Native, and React Three Fiber implement those operations for different platforms.

Ben Houston5 min read

This is Part 4 of the React Internals series. It builds on React Skips: How Rendering Optimization Works, which covered the work React can avoid before commit.

React's reconciler knows about fibers, hooks, lanes, Suspense, reconciliation, and skipped work. It does not know how to create a DOM node, mount a native view, or attach a Three.js object to a scene.

A renderer supplies that platform knowledge. react-dom, React Native, and React Three Fiber all let React use the same reconciler model against different hosts.

React reconciler sending host config methods to DOM, native, and Three.js renderers

The Reconciler Produces Host Work#

During render, React compares the next element tree with the current fiber tree. It may ask the renderer to create host instances for new host elements, but those instances are not visible yet. React also marks fibers with flags for work that must happen during commit: place this child in the visible tree, update these props, delete this subtree, run these effects.

During commit, React asks the renderer to perform the host operations that affect the committed tree. In the browser, those operations call DOM APIs. In React Native, they talk to the native view system. In React Three Fiber, they mutate Three.js objects.

The important distinction is naming:

  • The commit phase is React's phase for applying finished work.
  • commitUpdate is one host-config method a renderer may implement for applying prop changes to an existing host instance.

One is a phase. The other is a method used during that phase.

A Simplified Host Config#

A renderer provides a host config to the reconciler. The real host config has many methods, but this simplified shape captures the core operations:

type HostConfig<Instance, Props> = {
  createInstance: (type: string, props: Props) => Instance;
  appendInitialChild: (parent: Instance, child: Instance) => void;
  appendChild: (parent: Instance, child: Instance) => void;
  appendChildToContainer: (container: unknown, child: Instance) => void;
  removeChild: (parent: Instance, child: Instance) => void;
  commitUpdate: (instance: Instance, oldProps: Props, newProps: Props) => void;
};

createInstance creates a host object. appendInitialChild builds a new subtree while it is still detached from the visible host tree. appendChild inserts finished work under an existing host parent during commit. appendChildToContainer attaches finished host work to the root container during commit. removeChild detaches an existing child. commitUpdate changes an existing instance after props changed.

For the browser DOM, a host instance is usually a DOM node.

const button = document.createElement('button');
button.textContent = 'Save';
button.disabled = false;

React can create and assemble that node before it is visible. The moment that matters to the user is when commit inserts finished work into the live container:

parent.appendChild(button);

If the next render changes the button to disabled, React does not need a new button. The DOM renderer can commit an update to the existing instance:

button.disabled = true;

If the button disappears from the React tree, the DOM renderer removes the existing node during commit:

parent.removeChild(button);

The reconciler decides which of those operations is needed and when each one is safe. The renderer knows the platform API that performs it.

DOM, Native, and Three.js#

react-dom implements host operations with browser APIs. createInstance creates DOM nodes. Initial child appends can build a detached subtree. Commit-time insertion attaches that subtree to the live DOM. commitUpdate updates attributes, properties, styles, event handlers, and text where needed. removeChild detaches nodes.

React Native implements the same idea against platform views. A <View> maps to a native view. Prop updates become layout, style, and event updates through the native renderer. In the New Architecture, Fabric coordinates those changes with the native platform.

React Three Fiber maps React host instances to Three.js objects. A <mesh> becomes a THREE.Mesh. Appending a child calls .add() on an Object3D. Removing a child calls .remove(). Prop updates mutate objects, materials, geometry references, and other Three.js state.

The reconciler sees all of these as host instances. It does not care whether the host instance is an HTMLButtonElement, a native view, or a THREE.Mesh.

Refs Expose Renderer Instances#

Refs connect application code to renderer-owned instances.

In the DOM renderer, a ref on a host element gives you the DOM node:

function Form() {
  const inputRef = useRef<HTMLInputElement | null>(null);

  return <input ref={inputRef} />;
}

After commit, inputRef.current points at the HTMLInputElement. That is the renderer's public instance for the host element.

In React Three Fiber, a ref on <mesh> can point at a THREE.Mesh. In React Native, a ref can point at a native component handle or public instance shaped by that renderer.

Function components are different. A plain function component has no host instance:

function NameField() {
  return <input />;
}

Putting a ref on NameField does not automatically expose the inner input. The component must explicitly support refs and decide what public value to expose.

Why Renderers Stay Separate#

React's platform split lets the reconciler focus on UI structure and scheduling. Renderers focus on host behavior.

That separation has limits. Host platforms differ. DOM nodes have attributes, styles, text nodes, and browser events. Native views have layout systems and platform bridges. Three.js objects have scene graph parenting, materials, geometries, and GPU resources.

A renderer has to translate React's host operations into the platform's rules. It may also define which element names exist, which props are valid, how events work, and what a ref exposes.

Closing the Loop#

The series starts with the public update cycle, then follows React inward and back out:

  1. React Prerequisites names the lifecycle and hook vocabulary.
  2. React Fibers explains the tree and identity model.
  3. React Scheduling shows how React prioritizes work.
  4. React Skips: How Rendering Optimization Works explains how React avoids work.
  5. This article shows how finished work reaches the host.

That is the core loop: React receives an update, prepares a fiber tree, schedules and skips work where it can, commits finished host operations through a renderer, and leaves the browser or platform to display the result.