Skip to main content
  1. Posts/

How the new React Native architecture works (Fabric, JSI, Hermes) in simple terms

·4 mins

React Native has undergone a major architectural transformation. The classic Bridge + UIManager system has been replaced with a much more powerful design built on Fabric, JSI, and Hermes.

The old Bridge relied on a single-threaded communication channel between JavaScript and the native environment. As apps grew more complex, the Bridge became saturated and caused performance issues. The new architecture removes this bottleneck with a fully redesigned rendering pipeline.

In this article, I will explain the new architecture step by step and in simple terms, avoiding the vague explanations often found elsewhere.

We will break it down into two parts:

  1. Javascript side
  2. Native side

1. Javascript side #

JSX → React Elements → Fibers → Native #

Let’s walk through each layer to understand how your JSX becomes a native UI.


JSX #

JSX is a representation of UI nodes, such as:

  • <View>
  • <Text>
  • <Image>

Before anything else happens, JSX is transformed by Babel into React Elements.

<View>
  <Text>Hello</Text>
</View>

React Elements #

React Elements are plain JavaScript objects that describe your UI. They are created by calling:

React.createElement(View, null, "Hello");

Which returns objects like:

{
  $$typeof: Symbol(react.element),
  type: View,
  key: null,
  ref: null,
  props: {
    children: "Hello"
  }
}

React Elements are static descriptions and are passed to the Reconciler.


Reconciler (React Fiber) #

The Reconciler takes the React Elements and converts them into Fibers—data structures used internally by React to track updates, scheduling, and rendering work.

A simplified Fiber looks like this:

{
  tag: 5,
  type: Text,
  key: null,

  pendingProps: { children: "Hello" },
  memoizedProps: null,

  stateNode: null,
  return: parentFiber,
  child: null,
  sibling: null,

  flags: 0,
  lanes: 1
}

React compares each new Fiber with the previous Fiber to determine what needs to be created, updated, or deleted. These changes are represented as flags such as:

  • Placement (create)
  • Update
  • Deletion

As React processes nodes, it builds a new Fiber Tree. This new tree will eventually replace the current one once native updates are committed.

During the commit phase, the fibers that contain these flags are sent to the Host Config.


Host Config (React → Native interface) #

The Host Config is the interface between JavaScript and the native rendering layer. It is part of the Fabric architecture and is responsible for creating and updating Shadow Nodes on the native side.

A simplified interaction looks like this:

// When React sees a Placement (create), it runs something like:

const instance = hostConfig.createInstance(type, props, rootContainer, ...);
hostConfig.appendChild(parentInstance, instance);

// Then React assigns the created instance to the fiber:
fiber.stateNode = instance;

This triggers work in the native layer, where Fabric creates Shadow Nodes.

This is where we transition from the JavaScript world to the native world.


What is JSI? #

JSI (JavaScript Interface) replaces the classic Bridge.
Instead of sending JSON messages through a queue, JavaScript can directly call native code.

React Native uses Hermes, a JavaScript engine, and registers native modules like this:

runtime.global().setProperty(
   runtime,
   "NativeModuleProxy",
   jsi::Object::createFromHostObject(runtime, new MyModuleHostObject())
);

This allows you to call native code directly:

global.NativeModuleProxy.doSomething();

When React Native is compiled, its native modules are registered in the JS engine and become callable from JavaScript.


2. Native side #

Fabric is composed of:

  • Shadow Tree (and Shadow Nodes)
  • Yoga (layout engine)
  • Mounting Layer
  • UIManager

Shadow Tree #

When the Host Config requests node creation, the native side generates Shadow Nodes, which represent the UI hierarchy in C++.

A simplified Shadow Node before layout:

ShadowNode {
  tag: 42,
  surfaceId: 1,
  componentName: "View",

  props: {
    backgroundColor: "#FF0000",
    opacity: 1,
    pointerEvents: "auto",
    flexDirection: "column",
    padding: {top: 10, left: 0, right: 0, bottom: 0},
  },

  layoutMetrics: {
    frame: { x: 0, y: 0, width: UNDEFINED, height: UNDEFINED },
    padding: { top: 10, left: 0, right: 0, bottom: 0 },
    displayType: "flex",
    layoutDirection: "LTR",
  },

  children: [],
  revision: 1
}

Shadow Nodes are immutable, so updates create new nodes.


Yoga (Layout Engine) #

Yoga walks the Shadow Tree and calculates:

  • width
  • height
  • x/y position
  • margins
  • paddings

After Yoga, the ShadowNode contains final layout metrics:

layoutMetrics: {
  frame: { x: 5, y: 5, width: 100, height: 100 }
}

Mounting Layer #

Fabric compares the old Shadow Tree with the new one and generates mutations describing changes:

CreateView(tag: 30, componentName: "Text")
InsertChild(parentTag: 10, childTag: 30, index: 2)
UpdateLayout(tag: 30, frame: {...})

These mutations are passed to the UIManager.


UIManager #

UIManager executes each mutation using platform-specific APIs (UIView on iOS, View on Android):

for (auto &instruction : instructions) {
    surfacePresenter->execute(instruction);
}

This is where real native views are created, updated, moved, or deleted.


Summary #

  • JSX → transforms into React Elements
  • React Elements → processed by the Reconciler into Fibers
  • Changed Fibers → passed to Host Config
  • Host Config → creates Shadow Nodes
  • Yoga → computes layout
  • Fabric → diffs Shadow Trees and emits mutations
  • UIManager → applies mutations to native UI

Understanding these steps removes the “magic” feeling of React Native and clarifies how the new Fabric architecture works under the hood.