When I first started using React Native, I understood the code I was writing.
I knew what this meant:
<View>
<Text>Hello</Text>
</View>
I knew that View was something like a container and Text displayed text.
But I had never really asked a deeper question:
What actually happens between my React code and the pixels I see on the screen?
The code looks simple.
The result looks simple.
But there is a lot happening in between.
React Native has to take the React components we write in JavaScript, understand their structure, calculate where everything should appear, figure out what actually changed, and finally update the real native views on Android or iOS.
This is where concepts like Reconciliation, Fabric, Shadow Tree, Yoga, Commit, and Mount start to make sense.
Instead of learning these as separate terms, I found it much easier to understand them as different parts of the same journey.
So let's follow that journey.
The Code We Write
Let's start with something very simple.
function Welcome() {
return (
<View>
<Text>Hello</Text>
</View>
);
}
At the React level, this is just a component.
React sees something like:
Welcome
↓
<View>
↓
<Text>Hello</Text>
But Android and iOS don't understand React components.
Android doesn't have a native component called <View> in the React sense.
iOS doesn't know what <Text> means.
The operating system understands native UI objects.
So there is an important gap:
React Code
↓
?
↓
Native Views
↓
Pixels
What happens in that ?
That is the rendering system.
Why Does React Native Need a Rendering System?
If React already knows what the UI should look like, why can't React just directly create the native views?
Because there is more to rendering a UI than creating views.
Consider this:
<View style={{ flex: 1 }}>
<Text>Hello</Text>
</View>
Before Android or iOS can display this correctly, React Native needs to know things like:
- What is the parent-child structure?
- What size should the
Viewhave? - Where should the
Textbe positioned? - How much space is available?
- What happens if the screen size changes?
- What changed if state updates?
- Which native views actually need to be created?
- Which existing views can simply be updated?
React Native therefore needs an intermediate representation and a rendering process.
This is one of the most important ideas to understand:
React describes what the UI should look like. React Native's renderer figures out how that description becomes native UI.
And in the New Architecture, that renderer is called Fabric.
But before getting to Fabric, there is another React concept we need to understand.
Reconciliation: What Actually Changed?
Imagine we have a counter.
function Counter() {
const [count, setCount] = useState(0);
return (
<View>
<Text>{count}</Text>
<Button
title="Increase"
onPress={() => setCount(count + 1)}
/>
</View>
);
}
Initially:
0
After pressing the button:
1
React doesn't need to throw away the entire UI and build everything again.
It needs to understand what changed.
Conceptually:
Previous UI
<View>
<Text>0</Text>
<Button />
</View>
New UI
<View>
<Text>1</Text>
<Button />
</View>
The structure is mostly the same.
Only the text changed.
This process of React determining what the updated UI should look like is part of what we commonly call reconciliation.
The important idea is:
A React update does not mean every native view must be recreated.
The renderer can compare the previous and next representations and eventually apply only the necessary native changes.
This becomes especially important when an application becomes large.
Imagine a screen with:
Header
Profile
Statistics
Charts
Transactions
Buttons
Footer
If one transaction amount changes, we don't want to rebuild everything.
We want the renderer to understand the difference.
This is where Fabric becomes important.
Introducing Fabric
Fabric is React Native's new rendering system.
It is not a completely separate UI framework.
It is the rendering system that connects React's world with the native platform.
The architecture was redesigned around a shared C++ core, better interoperability with native platforms, and capabilities needed for modern React Native.
A simplified mental model looks like this:
React Components
↓
React Reconciliation
↓
Fabric Renderer
↓
Shadow Tree
↓
Layout Calculation
↓
Commit
↓
Mount
↓
Screen
But there is something important hiding inside this diagram.
Fabric doesn't immediately create native views when React renders.
Instead, it builds an intermediate representation.
This is the React Shadow Tree.
The Shadow Tree
When I first heard the term "Shadow Tree", I thought it was something complicated.
The basic idea is actually quite simple.
Suppose we write:
<View>
<Text>Hello</Text>
</View>
Conceptually, React has a tree:
View
└── Text
Fabric creates a corresponding tree of Shadow Nodes.
ViewShadowNode
│
└── TextShadowNode
A React Shadow Tree is created by Fabric and consists of React Shadow Nodes.
These nodes represent the React UI and contain information needed by the renderer, including props and layout information.
So instead of immediately saying:
"Create an Android View."
Fabric first builds a structured representation of what needs to exist.
This gives the renderer a place to reason about the UI before touching the actual native views.
Think of it like a plan.
React Code
<View>
<Text>Hello</Text>
</View>
↓
Shadow Tree
ViewShadowNode
│
└── TextShadowNode
↓
Native Views
Android / iOS Views
This separation is extremely useful.
Because now React Native can calculate, compare, and prepare changes before applying them to the actual UI.
But Where Does Layout Come From?
We now know that the Shadow Tree represents the UI.
But it still doesn't answer an important question.
Where exactly should each component appear?
For example:
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text>Hello</Text>
</View>
How does React Native decide:
View:
x = 0
y = 0
width = 390
height = 844
Text:
x = ...
y = ...
width = ...
height = ...
This is where Yoga comes in.
Yoga and Layout Calculation
React Native uses Yoga as its layout engine for calculating layout information.
You can think of Yoga as the part responsible for answering:
"Given these styles and these available constraints, where should everything go?"
For example:
<View
style={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text>Hello</Text>
</View>
Yoga helps calculate the position and size of the elements.
Conceptually:
Available screen
↓
Parent constraints
↓
Styles
↓
Yoga
↓
x / y / width / height
The result becomes layout information associated with the Shadow Tree.
It might conceptually look like:
View
x: 0
y: 0
width: 390
height: 844
└── Text
x: 160
y: 400
width: 70
height: 25
The exact values obviously depend on the device and content.
The important part is the responsibility.
Yoga calculates layout. Fabric uses that layout information as part of turning the Shadow Tree into native UI.
Render, Commit, Mount
At this point, we have enough context to understand the three words that appear again and again in React Native's rendering architecture:
Render.
Commit.
Mount.
These are not three unrelated concepts.
They are three phases of the rendering pipeline.
React Native's rendering pipeline can be understood as:
Render → Commit → Mount
Let's understand each one.
1. Render
Suppose we write:
<View>
<Text>Hello</Text>
</View>
React executes our application logic and produces the React Element Tree in JavaScript.
Conceptually:
React Component
↓
React Element Tree
Fabric then creates the corresponding React Shadow Tree.
React Element Tree
↓
React Shadow Tree
So the Render phase is essentially about constructing the next representation of the UI.
A simplified mental model is:
JavaScript / React
↓
React Elements
↓
Shadow Nodes
↓
Shadow Tree
The Shadow Tree is immutable.
That means React Native does not simply mutate the existing tree whenever something changes.
Instead, it creates a new version of the tree, while using structural sharing so unchanged portions don't need to be duplicated unnecessarily.
That sounds complicated, but the reason is important.
It makes the rendering system easier to reason about and allows different versions of UI state to be handled safely.
2. Commit
Once the new Shadow Tree has been created, React Native needs to prepare it for mounting.
This is the Commit phase.
Two important things happen here:
- Layout calculation
- Preparing the next tree for mounting
Yoga calculates the layout of the Shadow Tree using the available constraints and styles.
Then the newly prepared tree becomes the next tree that can be mounted.
Conceptually:
Shadow Tree
↓
Yoga
↓
Layout information
↓
Next Tree
So after Commit, React Native has something much closer to:
"This is the UI tree we want to display, and this is where everything should be."
But we still haven't actually updated the native views.
That happens next.
3. Mount
Now we finally reach the native UI.
The Mount phase takes the Shadow Tree, including its calculated layout, and turns it into the native Host View Tree.
This is where actual native views are created, updated, removed, or rearranged.
For example:
Shadow Node
↓
Native View
For Android, this can result in native Android views.
For iOS, corresponding UIKit views are used.
The renderer also compares the previously rendered tree with the next tree and produces the required mutations.
Conceptually:
Previous Tree
+
Next Tree
↓
Tree Diff
↓
create / update / remove
↓
Native Views
Finally:
Native Views
↓
Screen
Following One Example From Start to Finish
Let's put everything together.
Suppose we have:
function Greeting() {
return (
<View>
<Text>Hello</Text>
</View>
);
}
What happens?
Step 1 — React
React executes the component.
Greeting
↓
<View>
↓
<Text>Hello</Text>
React creates the React Element Tree.
Step 2 — Fabric
Fabric creates the corresponding Shadow Tree.
ViewShadowNode
│
└── TextShadowNode
Step 3 — Layout
Yoga calculates the layout.
View
├── x
├── y
├── width
└── height
Text
├── x
├── y
├── width
└── height
Step 4 — Commit
The newly prepared tree becomes the next tree to be mounted.
Step 5 — Mount
The renderer determines the required native operations.
Conceptually:
Create View
Create Text
Set Text = "Hello"
Add Text to View
Step 6 — Screen
The native platform displays the result.
Hello
That's the entire journey.
React Component
↓
React Element Tree
↓
Reconciliation
↓
Shadow Tree
↓
Yoga Layout
↓
Commit
↓
Mount
↓
Native Views
↓
Screen
What Happens When State Changes?
The initial render is only half the story.
The more interesting case is an update.
Consider:
function Counter() {
const [count, setCount] = useState(0);
return (
<View>
<Text>{count}</Text>
<Button
title="Increase"
onPress={() => setCount(count + 1)}
/>
</View>
);
}
Initially:
0
The user taps the button.
Now:
1
What happens?
First: React Updates
The state changes.
count: 0
↓
count: 1
React produces a new version of the React Element Tree.
Conceptually:
Previous
<View>
<Text>0</Text>
<Button />
</View>
Next
<View>
<Text>1</Text>
<Button />
</View>
The important thing is that React doesn't need to recreate everything conceptually from scratch.
The unchanged parts can be shared between the old and new trees.
Then: Shadow Tree Update
Fabric creates the updated Shadow Tree.
Conceptually:
Old Shadow Tree
View
├── Text("0")
└── Button
New Shadow Tree
View
├── Text("1")
└── Button
Notice something.
The View didn't disappear.
The Button didn't disappear.
The important change is the text.
Then: Commit
Yoga/layout work happens as needed.
The new tree becomes the next tree.
Finally: Mount
The renderer compares the previous rendered tree and the new tree.
Conceptually:
Previous
↓
Text = "0"
Next
↓
Text = "1"
Diff
↓
Update Text
So the native UI can perform a small update rather than rebuilding the entire screen.
This is one of the most important ideas to remember:
React re-rendering does not mean the entire native UI is recreated.
The renderer determines what native mutations are actually necessary.
Where Can Performance Problems Appear?
Understanding the rendering pipeline changes how I think about React Native performance.
Before learning this, it is easy to think:
"React Native performance means making JavaScript faster."
But that is only one part of the story.
There are multiple places where work can become expensive.
1. Too Much JavaScript Work
If your component logic is expensive, React may take longer to produce the next UI representation.
For example:
Large computation
↓
Slow render
↓
Delayed UI update
This is why things like unnecessary calculations, unnecessary component updates, and poorly structured state can matter.
2. Too Much UI
Imagine rendering hundreds or thousands of complex components.
Even if your JavaScript is reasonable, the renderer still has more UI to reason about.
Large lists are a classic example.
This is why list virtualization and careful component design matter.
3. Complex Layout
A complicated UI can require significant layout calculation.
For example:
Nested containers
↓
Many layout constraints
↓
More work
Yoga is efficient, but it still has work to do.
4. Too Many Native Mutations
Eventually, changes have to reach native views.
If an update causes a large number of native operations:
createView
updateView
removeView
insertView
...
that work can become expensive.
So performance isn't just about:
"How fast is my JavaScript?"
It is also about:
"How much work is required across the entire rendering pipeline?"
The Old Architecture vs The New Architecture
This is where many React Native discussions become confusing.
People often say:
"Old Architecture was Bridge and New Architecture is Fabric."
That's useful as a starting point, but it isn't the complete picture.
The New Architecture changed several major pieces of React Native's internals.
In the old architecture, React Native relied heavily on an asynchronous Bridge to serialize and enqueue communication between JavaScript and native code.
The New Architecture removes that dependency and introduces a new renderer, new native module/component systems, and better support for modern React capabilities.
A simplified comparison looks like this:
| Area | Legacy Architecture | New Architecture |
|---|---|---|
| Renderer | Legacy renderer | Fabric |
| JS ↔ Native communication | Asynchronous Bridge | JSI-based interoperability |
| Rendering model | Legacy rendering model | Modern renderer |
| Native modules | Legacy Native Modules | New Native Module system |
| Native components | Legacy system | New Native Component system |
| React capabilities | More limited by architecture | Better support for modern React features |
| Core rendering logic | More platform-specific | More shared C++ logic |
Starting with React Native 0.76, the New Architecture became enabled by default and was declared ready for production use.
But there is an important detail.
The New Architecture is not a magic switch that automatically makes every application fast.
Your application can still have:
Bad state management
↓
Unnecessary renders
↓
Heavy computations
↓
Huge lists
↓
Expensive UI
Changing the architecture does not remove application-level bottlenecks.
The architecture gives React Native better foundations and capabilities.
We still have to build our applications carefully.
What About Threads?
This is another area where React Native rendering can become confusing.
You may hear things like:
JavaScript Thread
UI Thread
Background Thread
And then try to memorize:
"Render always happens here, Commit always happens there, Mount always happens there."
That is not a good mental model.
The actual implementation and scheduling can vary.
React Native's rendering architecture involves JavaScript and C++ work, layout/commit work, and native UI work, with scheduling depending on the situation and platform.
So instead of memorizing:
Render = Thread X
Commit = Thread Y
Mount = Thread Z
I prefer to remember the responsibility:
Render
→ What should the UI tree look like?
Commit
→ Prepare the next tree and calculate its layout.
Mount
→ Apply the necessary changes to native UI.
That mental model survives implementation details much better.
Why Does the Shadow Tree Matter?
At this point, we can understand why the Shadow Tree exists.
Without an intermediate representation, the renderer would have to reason directly about platform-specific native views.
Instead, Fabric can work with a platform-independent representation:
React
↓
Shadow Tree
↓
Layout
↓
Diff
↓
Native Views
This creates an important separation.
React doesn't need to know whether the final platform is:
Android
or:
iOS
The renderer and host platform implementation handle that part.
This is also one reason Fabric's shared C++ core is important.
More rendering logic can be shared across platforms instead of being duplicated in completely separate implementations.
Why Does Mounting Use a Diff?
Imagine an application with this UI:
Screen
├── Header
├── Profile
├── Transactions
│ ├── Transaction 1
│ ├── Transaction 2
│ └── Transaction 3
└── Footer
Now imagine one transaction changes.
We don't want to blindly do:
Delete everything
Create everything again
Instead:
Previous Tree
+
Next Tree
↓
Compare
↓
Find changes
↓
Apply mutations
Maybe the result is simply:
Update Transaction 2
The Mount phase turns the difference between trees into the required native mutations.
This is one of the places where the architecture connects directly to something we care about as developers:
efficient UI updates.
The Complete Mental Model
Let's zoom out.
We started with:
<View>
<Text>Hello</Text>
</View>
But now we can see the bigger picture.
React Code
│
▼
React Element Tree
│
▼
Reconciliation
│
▼
Fabric Renderer
│
▼
Shadow Tree
│
▼
Yoga Layout
│
▼
Commit
│
▼
Previous Tree + Next Tree
│
▼
Diff
│
▼
Mount
│
▼
Native Views
│
▼
Screen
And the three phases can be remembered very simply:
RENDER
"What should the UI look like?"
COMMIT
"Prepare the next tree and calculate its layout."
MOUNT
"Apply the necessary changes to native UI."
The Bigger Picture
When I first started working with React Native, I mostly thought about components.
<View />
<Text />
<FlatList />
<Pressable />
Then I learned about state.
Then performance.
Then architecture.
But understanding the rendering pipeline added another layer to the picture.
A React Native application is not simply:
JavaScript → Screen
There is a rendering system between those two worlds.
JavaScript
↓
React
↓
Fabric
↓
Shadow Tree
↓
Layout
↓
Commit
↓
Mount
↓
Native UI
And suddenly terms that initially sounded unrelated start to connect.
Reconciliation helps determine the new React result.
Fabric is the rendering system.
Shadow Tree represents the UI before it becomes native views.
Yoga calculates layout.
Commit prepares the next tree.
Mount applies the necessary changes to the native UI.
These aren't separate topics.
They are different parts of one process.
One Simple Way to Remember It
If I had to explain the entire rendering system in an interview without going too deep into implementation details, I would say:
"React Native takes the React component tree produced by JavaScript and uses its renderer, Fabric, to build a Shadow Tree. During the render phase, React and the renderer create the next UI representation. During commit, layout is calculated using Yoga and the new tree is prepared. During mount, React Native diffs the trees and applies the required mutations to the native views."
That explanation is much more useful than memorizing a list of terms.
Because now every term has a responsibility.
React
→ describes the UI
Reconciliation
→ determines the new React result
Fabric
→ rendering system
Shadow Tree
→ intermediate representation
Yoga
→ layout calculation
Commit
→ prepare the next tree
Mount
→ update native UI
Final Thoughts
The interesting part of React Native is that the code we write is often much simpler than the system executing it.
When we write:
<View>
<Text>Hello</Text>
</View>
we don't normally need to think about:
React Element Tree
Shadow Nodes
Shadow Tree
Yoga
Commit
Tree Diffing
Mount
Native Views
And that's exactly what a good abstraction should do.
We can build applications without understanding every internal detail.
But when we start working on performance, debugging difficult rendering problems, building native components, or simply trying to understand why React Native works the way it does, these concepts become extremely useful.
For me, the biggest takeaway is not memorizing the names.
It is understanding the journey:
React describes the UI. Fabric builds a representation of that UI. Yoga calculates its layout. Commit prepares the next tree. Mount turns the result into native UI.
Once that mental model is clear, React Native rendering stops feeling like a collection of mysterious internal terms.
It becomes one connected system.
And that is the part worth remembering.










Top comments (0)