The Fabric Renderer: Building Custom Native Views as Custom Components
AI generated
RN
native
React Native / New Architecture
The Fabric Renderer
Rendering custom native views synchronously as custom components

Fabric replaces the asynchronous bridge with a shared C++ shadow tree that JavaScript and native code access directly through JSI. Building a custom native view as a custom component means understanding how ComponentDescriptor, ShadowNode and generated codegen code work together, from the first spec file to the finished component view on iOS and Android.

11 min read Fabric ShadowNode ComponentDescriptor

1. From the asynchronous bridge to the synchronous Fabric pipeline

In the original React Native architecture, every piece of communication between JavaScript and native code ran through the bridge: an asynchronous channel that serialized messages as JSON, batched them and sent them in both directions through a message queue. For rendering, that meant JavaScript described a shadow tree, which was sent across the bridge to the native side, had its layout computed with Yoga, and only then produced or updated the actual native views. Every step in that chain cost time for serialization and thread switching, which became noticeable with complex lists or frequent updates.

Fabric replaces that chain with a shared C++ core layer that JavaScript and native code both address directly through JSI. The shadow tree exists only once, in C++, and both the JavaScript runtime and iOS and Android access it by reference instead of shipping copies across a bridge. That makes layout computation and the commit phase synchronous, which matters especially for custom components that need to know precisely when a new version of their shadow node has actually landed on screen.

2. ComponentDescriptor and ShadowNode: the building blocks of a Fabric component

Every Fabric component consists of at least three C++ classes: a ShadowNode holding the immutable state of a component instance in the shadow tree, a ComponentDescriptor telling Fabric how to create, clone and supply props to a ShadowNode, and a props class mapping the typed properties coming from JavaScript. These three building blocks are deliberately separated from the actual platform view: the shadow tree only knows about layout and props, not UIKit or the Android view system.

Only the ComponentView on iOS, or the equivalent Fabric view adapter on Android, translates a ShadowNode into a visible native view. This separation makes it theoretically possible to reuse the same ShadowNode for different rendering targets, for example off-screen rendering or tests without a real UI, something that was practically impossible with the old view manager model.

3. From TypeScript spec to generated Fabric interface

A custom Fabric component does not start with C++, but with a TypeScript spec file that codegen reads as the contract between JavaScript and the native side. It declares props, events and the component type, usually through codegenNativeComponent, and codegen generates the matching C++ headers for the props classes, Objective-C interfaces for iOS and Java interfaces for Android at build time.

The big advantage over the old view manager API is that type mismatches between JavaScript and native side surface at build time, not as a silent crash or misinterpreted prop at runtime. Adding a new property only requires changing the spec file, running codegen, and immediately seeing which native files need updating, because the generated code changes accordingly.

4. Building a custom Fabric view on iOS: from ComponentDescriptor to ComponentView

On iOS a Fabric component subclasses RCTViewComponentView, which receives the typed props handed down from the shadow tree in updateProps and translates them into native UIKit properties. Unlike the old view manager approach, where properties were set individually through key value pairs, a complete, immutable props object arrives here that can be compared efficiently against the previous state to detect actual changes.

The component is registered through a static supportedViewConfigCls method and a Fabric component descriptor provider registration entered into the global component registry at app startup. Missing this registration makes Fabric silently fall back to an empty view for the affected component, a debugging pitfall that regularly causes confusion in practice.

5. Building a custom Fabric view on Android: ViewManager meets ComponentDescriptor

On Android the classic ViewManager remains the connecting piece but gains a Fabric-specific layer: the generated ViewManagerDelegate maps props from the generated Java interface onto concrete setter methods, so the actual ViewManager class only needs to implement the setters without handling reflection or manual prop parsing.

Every Fabric-capable Android component also needs an entry in the ReactPackage that supplies both the view manager and the matching component descriptor name, so the native runtime knows during shadow tree construction which C++ component descriptor implementation is responsible for a given component name. A misspelled name here also leads to a silently empty view instead of a clear error message.

6. Synchronous state management without a round trip through the bridge

A central feature of Fabric is the ability to hold native state directly inside the shadow tree instead of sending every state change back to JavaScript through a full React re-render cycle. Through the generated State class, a native component, say a scroll offset or a text measurement, can commit its state directly, and Fabric ensures that state is consistently reconciled with the next layout pass.

This matters especially for components that change state frequently and at high rates, for example a native text field with cursor position or a scroll view with momentum scrolling. Without this mechanism, every position change would first have to pass through the JavaScript layer before reaching the shadow tree, which would cause noticeable delays at input rates of 60 or 120 hertz.

7. The event pipeline: how interactions travel synchronously through Fabric

Events like touches or layout changes are dispatched in Fabric through a dedicated EventEmitter instance per shadow node, prioritized and batched on the C++ side before being handed to the JavaScript runtime. What matters for custom components is that events marked as direct go straight to the registered JavaScript handler without the detour through the classic bubbling logic of the old system.

The spec file determines through the event type whether an event is prioritized synchronously, for example for continuous feedback like drag gestures, or treated as a lower priority, discrete event. Choosing this priority incorrectly risks either unnecessary rendering pressure for trivial events or noticeable lag on time-critical interactions like a slider.

8. Migrating legacy view manager code: the interop layer as a bridge

For libraries that do not yet have a native Fabric component, the Fabric interop layer automatically wraps old view manager based components in a compatible Fabric shell. That allows a gradual transition where not every third-party component needs to be rewritten immediately, but it costs part of the performance benefit because the old prop diffing mechanism is still used internally.

For a real migration it pays off to first write the props and events spec, then implement the generated interfaces, and only remove the old view manager code at the end instead of maintaining both in parallel. In practice, the bigger effort usually is not the C++ part but cleanly migrating imperative commands that used to run through UIManager.dispatchViewManagerCommand and now need their own typed commands in the spec.

9. Performance characteristics and debugging Fabric components

The most noticeable performance effect of Fabric shows up in lists with many simultaneously visible custom components, because layout computation and commit run synchronously without bridge serialization, noticeably reducing jank during fast scrolling. For custom components it is worth watching the number of re-renders in the shadow tree, which can be observed through Flipper or the built-in Fabric logging.

Classic sources of bugs are forgotten component descriptor registrations, which lead to empty views without an error message, and crashes caused by thread-unsafe access to UIKit or Android view objects from a background thread, since Fabric deliberately performs layout computation off the main thread. Anyone debugging a custom Fabric component should therefore first check whether UI mutations actually land on the main thread before diving deeper into the C++ layer.

Aspect Old bridge architecture Fabric Impact on custom components
Data exchange Asynchronous JSON serialization over the bridge Direct C++ access through JSI, no serialization Less latency for props and state updates
Shadow tree Separate copies in JS and native One shared C++ instance Consistent state without sync bugs
Layout commit Asynchronous, often visibly delayed Synchronous within the same render cycle Less layout flicker for dynamic views
Prop typing Manual, runtime errors on typos Codegen-generated, checked at build time Errors caught earlier when adding props
Imperative commands dispatchViewManagerCommand with string names Typed commands from the spec Fewer runtime crashes from wrong command names
Registration View manager class with manual setup ComponentDescriptor provider in a global registry Missing registration surfaces early as an empty view
Debugging tool Chrome DevTools without native insight Flipper with Fabric-specific logging Re-renders and commits in the shadow tree directly visible

Mironsoft

React Native app development and Magento integration

A mobile app for the Magento shop that actually runs smoothly?

We build React Native apps cleanly connected to the Magento REST or GraphQL API, from the first line of code to publishing on the App Store and Google Play.

App Concept

Plan the architecture and feature scope of a Magento-connected app together.

Magento API Integration

Cleanly connect product catalog, cart, and checkout to the shop API.

Store Publishing

Guide the App Store and Google Play release process without pitfalls.

10. Summary

Fabric Custom Components at a Glance

ShadowNode & ComponentDescriptor

Form the C++ foundation of every Fabric component, fully separated from the visible platform view.

Codegen

Generates typed interfaces for iOS and Android from a TypeScript spec, catching errors at build time instead of runtime.

Synchronous state

Lets native components commit frequent state directly in the shadow tree without a round trip through JavaScript.

Interop layer

Eases a gradual migration of old view manager components, at the cost of part of the performance benefit.

11. FAQ: Fabric Custom Components at a Glance

1What is the difference between a ShadowNode and a ComponentView?
The ShadowNode is an immutable C++ representation in the shadow tree that only knows about props and layout. The ComponentView is the actual native platform view created from the ShadowNode.
2Do I have to write C++ for a custom Fabric component?
Usually not directly, since codegen generates the C++ interfaces from the TypeScript spec. The actual implementation happens in Swift/Objective-C++ on iOS and Kotlin/Java on Android.
3What happens if I forget the ComponentDescriptor registration?
Fabric silently renders an empty view instead of an error, which in practice is a common debugging pitfall.
4Can I keep using old view manager components?
Yes, through the Fabric interop layer, which automatically wraps them in a compatible shell, though with fewer performance benefits than a native Fabric component.
5How does state in Fabric differ from React state?
Fabric state is committed directly in the shadow tree and does not need a full round trip through JavaScript first, making it noticeably faster for high-frequency changes.
6Where do I define whether an event is synchronously prioritized?
In the TypeScript spec file through the event type, telling Fabric whether an event is continuous feedback like a gesture or a discrete, lower priority event.
7Why does my Fabric component crash on UI updates?
Usually because of thread-unsafe access to UIKit or Android view objects from a background thread, since Fabric deliberately performs layout computation off the main thread.
8Are imperative commands still possible in Fabric?
Yes, but typed through the spec file instead of generic, string-based dispatchViewManagerCommand calls as in the old architecture.
9Is a Fabric migration worth it for small, rarely used custom components?
Often not immediately, since the interop layer offers sufficient compatibility for rare, non-critical components, while the migration effort clearly pays off for high-frequency components like list items.
10Which tool is best for debugging Fabric components?
Flipper together with the built-in Fabric logging shows re-renders and commit cycles in the shadow tree and helps track down unnecessary updates in custom components.