React Native Rendering, Part 6: The Fabric Render Pipeline — Render, Commit, Mount
We now have every piece the new renderer needs. From Part 3: JSI, a direct line between JavaScript and C++. From Part 4: the Shadow Tree, an immutable C++ host tree with structural sharing. From Part 5: Yoga, the layout engine that turns styles into geometry.
This article assembles them into the thing they were built for: Fabric, React Native's new renderer — and the direct counterpart of the browser's style → layout → paint → composite. Where the browser runs that whole pipeline on one thread, Fabric runs it as three phases across three threads, over immutable revisions.
What You'll Learn
- What Fabric is, and how it differs from the legacy renderer
- The three phases — Render, Commit, Mount — and which thread each runs on
- What happens in each phase: building/cloning the Shadow Tree, Yoga layout, tree diffing, and mounting
- How initial renders differ from updates, and how "tree promotion" keeps everything straight
- The map from the browser's pipeline to Fabric's — and why Fabric's shape is what concurrent React needed
Prerequisites
- JSI — the direct, synchronous bridge between JS and C++:
- The Shadow Tree — Fabric's host tree, and why it is immutable:
- Yoga — how layout is computed:
Starting from a Scenario
Recall the tooltip from Part 2. To place it above a button, you measured the button with onLayout, then called setState — and for one frame, the tooltip appeared in the wrong place, then jumped. The cause was the legacy architecture: layout was asynchronous, and there was no way to read it before the first paint.
Now run the same idea on the New Architecture, with a layout effect:
function Tooltip() {
const ref = useRef(null);
const [y, setY] = useState(0);
useLayoutEffect(() => {
// read layout synchronously and update before the frame is shown
ref.current?.measure((x, y, w, h, pageX, pageY) => {
setY(pageY);
});
}, []);
return <View ref={ref} style={[styles.tooltip, { top: y - 40 }]} />;
}No flash. No jump. The tooltip is positioned before it is shown. What changed? Not your code style — the renderer. Fabric can compute and expose layout synchronously, on demand, because of how its pipeline is built.
This article is about that pipeline.
Core Content
1. What Fabric is
Fabric is React Native's new renderer — "a conceptual evolution of the legacy render system." Its goals are explicit:
- Unify more rendering logic in C++, so the tricky parts are written once and shared across iOS and Android.
- Interoperate better with host platforms, including synchronous, thread-safe layout.
- Unlock new capabilities — above all, concurrent React.
Three of its foundations are the parts we already built: it communicates through JSI (no serialization), it operates on the immutable Shadow Tree, and it lays out with Yoga. On top of those, it defines a pipeline of three phases.
2. The three phases
Fabric's render pipeline has three phases. Their names — Render, Commit, Mount — are worth memorizing, because they map cleanly onto the browser's stages and onto the threads from Part 2.
Phase Thread What happens
────── ────────────── ────────────────────────────────────────────
RENDER JS thread React runs your code and builds the
React Element Tree; the renderer creates or
clones the Shadow Tree (in C++, via JSI).
COMMIT background Layout Calculation (Yoga) over the Shadow Tree;
thread Tree Promotion: new tree -> "next" tree.
MOUNT UI thread Tree Diffing: compute minimal mutations;
Tree Promotion: next tree -> "rendered" tree;
View Mounting: apply mutations to native views.Notice what this buys compared to the legacy architecture (Part 2): the three phases still happen on three different threads, but now there is no serialization between them, and they hand off immutable revisions rather than mutable messages.
Let's walk each phase.
3. The Render phase (JS thread)
An update begins on the JS thread, where React lives.
- React runs your components and produces a React Element Tree — the same reconciliation you know from the web.
- For each host component (
View,Text, …), the renderer creates or clones a ShadowNode in the Shadow Tree, directly through JSI. This is the "render" work: building the C++ description of what the UI should be. - On an update, React does not rebuild the whole tree. Thanks to the immutability and structural sharing from Part 4, it clones only the path from each changed node up to the root; unchanged subtrees are shared.
- The result is a new revision of the Shadow Tree — conceptually "what the UI should look like next."
The render phase ends with a commit: the new tree is ready to be laid out and mounted.
4. The Commit phase (background thread)
Now the tree is complete, but nothing knows where anything goes yet. The commit phase does two things:
Layout Calculation. The renderer runs Yoga over the Shadow Tree, computing each node's frame (x, y, width, height) — exactly the process from Part 5. Most of it is pure C++, but for components whose size depends on the host platform (text, text inputs), Yoga calls back into the platform's measure function.
Tree Promotion (New → Next). Once layout is done, the new Shadow Tree is promoted to be the "next tree" — the tree that is ready to be mounted. It now carries everything needed: structure, props, and resolved layout.
This phase runs on a background thread, asynchronously. That is why — in the New Architecture too — layout is not something you get for free in the same synchronous step as rendering. When you do need it synchronously, Fabric provides a way to ask; more below.
5. The Mount phase (UI thread)
Finally, the "next tree" becomes pixels. Mounting happens on the UI thread, and it has three steps:
Tree Diffing. The renderer computes the difference between the previously rendered tree and the next tree, entirely in C++. The output is a list of atomic mutation operations: createView, updateView, insertChild, removeChild, deleteView, and so on. Only what actually changed becomes a mutation — a color change produces a single updateView, not a rebuild.
Tree Promotion (Next → Rendered). The "next tree" is atomically promoted to become the "previously rendered tree." The next mount phase will diff against this one, so the bookkeeping stays correct.
View Mounting. The mutations are applied to the host view tree — real UIViews and Android Views. If commit happened on the background thread, mounting is scheduled for the next tick of the UI thread; if commit happened on the UI thread, mounting runs synchronously.
6. Initial render vs. updates
The same pipeline covers both, with one difference worth seeing clearly.
- Initial render. There is no "previously rendered tree," so tree diffing produces only creations: create the views, set their props, and attach children. There is nothing to compare against.
- State update. React clones the affected path (structural sharing), Yoga re-lays out what changed, and diffing compares the new tree against the old. The result is a minimal mutation list — often just a single property change on a single view.
- Diffing can skip versions. Because any two trees can be diffed, the renderer can compare the currently mounted tree directly against the newest tree, skipping any intermediate revisions that were superseded before they could mount. Old work is simply dropped.
7. A second entry point: C++ State updates
Not every update starts in React. Some originate in C++ — for example, certain animations that update a node's state (the state slot from Part 4). When that happens:
- There is no React render phase at all — React is not involved.
- The renderer repeatedly tries to clone the latest committed version of the node with the new state and commit it; if another commit happened in the meantime, it retries. This avoids races between JS-originated and C++-originated updates.
This is the mechanism that lets some animations update the UI without crossing into JavaScript on every frame — a preview of Part 7.
8. The map: browser pipeline vs. Fabric pipeline
Here is the centerpiece — the browser's single-threaded pipeline next to Fabric's three-threaded one:
| Browser (one thread) | Fabric (three threads) | Thread |
|---|---|---|
| Parse HTML → DOM | React → Shadow Tree (via JSI) | JS |
| Style (match CSS, compute styles) | apply typed Props to ShadowNodes | JS |
| Layout (compute boxes) | Commit: Yoga layout | background |
| — (bookkeeping) | Commit: tree promotion | background |
| Paint (draw commands) + Composite | Mount: diff + apply to native views; the OS composites | UI |
The shapes are the same — describe, compute geometry, put pixels on screen — but Fabric rebuilds them for a world the browser never had to face: three threads, two languages, and one immutable tree connecting them.
9. Why this shape is what concurrent React needed
Look back at Part 2's table of what concurrent React requires, and check it against Fabric:
- Read layout synchronously? Fabric can — layout lives in C++ and is reachable through JSI, so a layout effect can read it without an async round trip. (Hence the tooltip in the scenario.)
- Interrupt and resume a render? Immutable revisions mean an in-progress tree and the currently shown tree can coexist without conflict. A low-priority render can be interrupted by a high-priority one and dropped or resumed.
- Multiple in-progress trees? Structural sharing makes keeping several revisions cheap.
- Render on the UI thread for urgent updates? Because mounting is scheduled on the UI thread, an urgent update can be rendered synchronously there, interrupting background work.
Each of those was impossible in the legacy architecture and is possible now — not automatic, but possible. Turning possibility into a smooth user experience is the subject of the final article.
Hands-On Practice
Basic Exercises
-
Say the three phases. For a simple state change (toggling a box's color), name what happens in Render, Commit, and Mount — and which thread each runs on.
-
Predict the mutations. For that color toggle, predict the mutation list. (It is one
updateView.) Then for inserting a new child at the front of a list. Then for removing a node. -
Find the machinery. In the React Native source, find the
Differentiator(tree diffing) and theMountingCoordinator(handing mutations to the platform). You are looking at the Mount phase's engine.
Advanced Reflection
Render, Commit, and Mount hand off immutable revisions across three threads, with no locks. Using Part 4's immutability, explain why that is safe — and what the "two tree promotions" (new→next, next→rendered) are really keeping track of.
Summary & Keywords
- Fabric is React Native's new renderer: C++-based, cross-platform, built on JSI, the Shadow Tree, and Yoga.
- Its pipeline has three phases: Render (JS thread — build/clone the Shadow Tree), Commit (background — Yoga layout + promote to "next tree"), Mount (UI thread — diff, promote to "rendered", apply minimal mutations to native views).
- Initial render produces only creations; updates clone only the changed path (structural sharing) and diff to a minimal mutation list, and can skip superseded revisions.
- A separate path — C++ state updates — can update the UI without involving React's render phase.
- The pipeline is the browser's style → layout → paint → composite rebuilt for three threads and one immutable tree — and that shape is precisely what concurrent React needed.
Keywords: Fabric, render pipeline, Render/Commit/Mount, tree diffing, tree promotion, mutation operations, mounting, view flattening, C++ state, concurrent React
Further Reading and Next Article Preview
The machinery is complete. The last question is the one users actually feel: does it stay smooth? The final article brings us back to where we started — a stuttery, JS-driven animation — and follows the New Architecture's answer: priorities, interruptible renders, synchronous layout reads, and moving animation work off the JS thread entirely.
Next up: React Native Rendering, Part 7: Concurrency and Animation — Making Updates Feel Instant.
