React Native Turbo Modules & Fabric

Understanding the New Architecture: Advantages and Implementation

Introduction

React Native's new architecture represents a fundamental reimagining of how the framework operates. At its core are two key innovations: Turbo Modules and Fabric. Together, they address long-standing performance bottlenecks and pave the way for a faster, more efficient React Native.

Quick Overview:
  • Turbo Modules: A new native module system that loads modules lazily and enables synchronous communication
  • Fabric: A complete rewrite of the UI rendering system with concurrent rendering capabilities

Turbo Modules: The New Native Module System

What Are Turbo Modules?

Turbo Modules replace the legacy native modules system with a more efficient architecture. Instead of loading all native modules at startup, Turbo Modules are loaded lazily when needed, dramatically reducing startup time.

Key Advantages

⚡ Lazy Loading

Modules are loaded on-demand rather than at startup, reducing initial bundle size and improving app launch time.

🔄 Synchronous Calls

Support for synchronous native method calls eliminates callback complexity for simple operations.

📘 Type Safety

Built-in TypeScript support with code generation ensures type safety across the JavaScript-Native bridge.

🚀 Better Performance

Optimized bridge communication reduces overhead and improves data serialization.

Creating a Turbo Module

Let's create a simple Turbo Module for device information:

NativeDeviceInfo.ts (JavaScript Spec)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  // Synchronous method
  getDeviceId(): string;
  
  // Asynchronous method
  getBatteryLevel(): Promise<number>;
  
  // Method with parameters
  logEvent(eventName: string, params: Object): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>(
  'DeviceInfo'
);
Android Implementation (Kotlin)
package com.myapp

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise

class DeviceInfoModule(reactContext: ReactApplicationContext) :
    NativeDeviceInfoSpec(reactContext) {
    
    override fun getName(): String {
        return NAME
    }
    
    // Synchronous method
    override fun getDeviceId(): String {
        return android.provider.Settings.Secure.getString(
            reactApplicationContext.contentResolver,
            android.provider.Settings.Secure.ANDROID_ID
        )
    }
    
    // Asynchronous method
    @ReactMethod
    override fun getBatteryLevel(promise: Promise) {
        try {
            val batteryManager = reactApplicationContext
                .getSystemService(Context.BATTERY_SERVICE) as BatteryManager
            val level = batteryManager.getIntProperty(
                BatteryManager.BATTERY_PROPERTY_CAPACITY
            )
            promise.resolve(level)
        } catch (e: Exception) {
            promise.reject("ERROR", e.message)
        }
    }
    
    @ReactMethod
    override fun logEvent(eventName: String, params: ReadableMap) {
        // Implementation
    }
    
    companion object {
        const val NAME = "DeviceInfo"
    }
}
iOS Implementation (Objective-C)
#import <React/RCTBridgeModule.h>
#import "RNDeviceInfoSpec.h"

@interface DeviceInfo : NSObject <NativeDeviceInfoSpec>
@end

@implementation DeviceInfo

RCT_EXPORT_MODULE()

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
    (const facebook::react::ObjCTurboModule::InitParams &)params {
    return std::make_shared<facebook::react::NativeDeviceInfoSpecJSI>(params);
}

// Synchronous method
- (NSString *)getDeviceId {
    return [[[UIDevice currentDevice] identifierForVendor] UUIDString];
}

// Asynchronous method
- (void)getBatteryLevel:(RCTPromiseResolveBlock)resolve
                 reject:(RCTPromiseRejectBlock)reject {
    [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];
    float batteryLevel = [[UIDevice currentDevice] batteryLevel];
    if (batteryLevel < 0) {
        reject(@"ERROR", @"Battery level unavailable", nil);
    } else {
        resolve(@(batteryLevel * 100));
    }
}

- (void)logEvent:(NSString *)eventName params:(NSDictionary *)params {
    // Implementation
}

@end
Usage in React Native
import DeviceInfo from './specs/NativeDeviceInfo';

function MyComponent() {
  const [deviceId, setDeviceId] = useState('');
  const [battery, setBattery] = useState(0);
  
  useEffect(() => {
    // Synchronous call
    const id = DeviceInfo.getDeviceId();
    setDeviceId(id);
    
    // Asynchronous call
    DeviceInfo.getBatteryLevel().then(level => {
      setBattery(level);
    });
    
    // Log event
    DeviceInfo.logEvent('screen_view', {
      screen_name: 'Home'
    });
  }, []);
  
  return (
    <View>
      <Text>Device ID: {deviceId}</Text>
      <Text>Battery: {battery}%</Text>
    </View>
  );
}

Fabric: The New Rendering System

What is Fabric?

Fabric is React Native's new rendering engine that replaces the legacy UI Manager. It enables concurrent rendering, improves interop with native views, and provides better performance for complex UIs.

Key Advantages

🎯 Concurrent Rendering

Supports React 18's concurrent features like Suspense and automatic batching for smoother UIs.

⚙️ Simplified Architecture

Direct communication between JavaScript and native host views without multiple intermediate layers.

🔧 Better Native Interop

Improved integration with native views and easier embedding of React Native in existing apps.

📱 Cross-Platform Consistency

More consistent behavior between iOS and Android through shared C++ core.

Creating a Fabric Component

Here's how to create a custom native component using Fabric:

CustomButtonNativeComponent.ts (Component Spec)
import type { ViewProps } from 'react-native';
import type { HostComponent } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';

export interface NativeProps extends ViewProps {
  title: string;
  color?: string;
  onPress?: () => void;
}

export default codegenNativeComponent<NativeProps>(
  'CustomButton'
) as HostComponent<NativeProps>;
Android Implementation (Kotlin)
class CustomButtonView(context: Context) : AppCompatButton(context) {
    
    private var onPressListener: (() -> Unit)? = null
    
    init {
        setOnClickListener {
            onPressListener?.invoke()
        }
    }
    
    fun setTitle(title: String) {
        text = title
    }
    
    fun setColor(color: Int) {
        setBackgroundColor(color)
    }
    
    fun setOnPress(listener: (() -> Unit)?) {
        onPressListener = listener
    }
}

class CustomButtonViewManager : SimpleViewManager<CustomButtonView>() {
    
    override fun getName() = "CustomButton"
    
    override fun createViewInstance(context: ThemedReactContext): CustomButtonView {
        return CustomButtonView(context)
    }
    
    @ReactProp(name = "title")
    fun setTitle(view: CustomButtonView, title: String) {
        view.setTitle(title)
    }
    
    @ReactProp(name = "color", customType = "Color")
    fun setColor(view: CustomButtonView, color: Int?) {
        view.setColor(color ?: Color.BLUE)
    }
    
    override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
        return mapOf(
            "onPress" to mapOf("registrationName" to "onPress")
        )
    }
}
iOS Implementation (Objective-C)
#import <React/RCTViewManager.h>
#import <React/RCTUIManager.h>

@interface CustomButtonViewManager : RCTViewManager
@end

@implementation CustomButtonViewManager

RCT_EXPORT_MODULE(CustomButton)

- (UIView *)view {
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    [button addTarget:self 
               action:@selector(buttonPressed:)
     forControlEvents:UIControlEventTouchUpInside];
    return button;
}

RCT_EXPORT_VIEW_PROPERTY(title, NSString)
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTDirectEventBlock)

- (void)buttonPressed:(UIButton *)sender {
    if (self.onPress) {
        self.onPress(@{});
    }
}

@end
CustomButton.tsx (JavaScript Wrapper)
import React from 'react';
import { StyleSheet } from 'react-native';
import CustomButtonNativeComponent from './CustomButtonNativeComponent';

interface Props {
  title: string;
  color?: string;
  onPress?: () => void;
}

export default function CustomButton({ title, color, onPress }: Props) {
  return (
    <CustomButtonNativeComponent
      style={styles.button}
      title={title}
      color={color}
      onPress={onPress}
    />
  );
}

const styles = StyleSheet.create({
  button: {
    height: 50,
    paddingHorizontal: 20,
  },
});
Usage Example
import CustomButton from './components/CustomButton';

function App() {
  return (
    <View style={styles.container}>
      <CustomButton
        title="Click Me!"
        color="#667eea"
        onPress={() => console.log('Button pressed!')}
      />
    </View>
  );
}

Enabling the New Architecture

To enable Turbo Modules and Fabric in your React Native project:

Android (android/gradle.properties)
# Enable new architecture
newArchEnabled=true
iOS (Run in ios/ directory)
# Enable new architecture
RCT_NEW_ARCH_ENABLED=1 pod install
Note: The new architecture requires React Native 0.68 or higher. For the best experience, use React Native 0.71 or later where the new architecture is more stable.

Migration Considerations

  • Audit existing native modules and components for compatibility
  • Update third-party libraries to versions that support the new architecture
  • Test thoroughly on both iOS and Android
  • Consider gradual migration by enabling only for specific modules first

Performance Benefits

Real-world applications have reported significant improvements after migrating:

Startup Time

Up to 50% reduction in app initialization time due to lazy module loading

Frame Rate

Smoother animations and scrolling with concurrent rendering

Memory Usage

Lower memory footprint from optimized data structures

Bundle Size

Smaller initial bundles through code-splitting capabilities