DEV Community

Cover image for React Native 0.76: The New Architecture That Takes Performance and Efficiency to the Next Level 🚀
Abdulnasır Olcan
Abdulnasır Olcan

Posted on

React Native 0.76: The New Architecture That Takes Performance and Efficiency to the Next Level 🚀

React Native 0.76 version comes with a comprehensive architectural overhaul to enhance performance, security, and provide a smoother user experience. With this update, the Fabric Renderer and TurboModules make in-app transitions faster and smoother, while memory usage is optimized through auto-loaded modules. The integration of the new JSI and Hermes engine, in particular, ensures secure, high-performance communication with native modules. A closer look at the details and practical applications reveals a versatile toolkit for React Native developers.

Key Features:

  • TurboModules: Loads only the required modules.
  • Fabric Renderer: Full support for React 18’s features with concurrent rendering.
  • Hermes Integration: Low memory usage and fast processing.

1. Fabric Renderer

Fabric is a redesigned UI rendering system in React Native. It removes the traditional asynchronous bridge, which often caused bottlenecks, enabling JavaScript and native components to work more directly together.

  • Synchronous Communication: Fabric establishes a direct connection with native components, enabling instantaneous, real-time updates.
  • Partial Tree Updates: Unlike the previous architecture, Fabric renders only the components that have changed, reducing the need for re-rendering and conserving resources.
  • Goal: To enable React 18's automatic batching and concurrent rendering with a new-generation rendering engine.

Example:

import React, { useState, useEffect } from 'react';
import { Text, View, Platform } from 'react-native';

const useSynchronizedFabricComponent = (text) => {
  // Custom hook simulating Fabric's synchronous behavior
  return text.toUpperCase(); // Fabric instantly reflects this update
};

const FabricEnhancedComponent = () => {
  const [message, setMessage] = useState("Hello Fabric!");
  const displayMessage = useSynchronizedFabricComponent(message);

  useEffect(() => {
    if (Platform.OS === 'android') {
      setMessage("Fabric Running on Android!");
    }
  }, []);

  return (
    <View style={{ padding: 20 }}>
      <Text>{displayMessage}</Text>
    </View>
  );
};

export default FabricEnhancedComponent;

Enter fullscreen mode Exit fullscreen mode

In this example, Fabric instantly renders displayMessage, making the UI smoother with synchronous processing.

2. Turbo Modules and JSI

Turbo Modules replace the traditional bridge with a more efficient system that accelerates interaction between JavaScript and native code. This system ensures that only the required modules are loaded, thus reducing memory usage.

  • On-Demand Loading: Modules are loaded only when needed, which reduces memory usage.
  • Performance Boost: JavaScript calls to native functions are faster, minimizing interaction delays.
  • Goal: To eliminate synchronization issues caused by the bridge architecture and provide fast, type-safe access to modules.
  • Details: The JavaScript Interface (JSI) enables direct, bridge-free communication between JavaScript and native modules. TurboModules allow for performance gains by loading modules only as needed.

Example:

// Directly calling the native module from JavaScript
import { NativeModules } from 'react-native';
const { ExampleModule } = NativeModules;
ExampleModule.showToast("Hello!");

Enter fullscreen mode Exit fullscreen mode

Example:

import { NativeModules } from 'react-native';
const { BatteryStatusModule } = NativeModules;

const fetchBatteryLevel = async () => {
  try {
    const level = await BatteryStatusModule.getBatteryLevel();
    console.log("Current Battery Level:", level);
  } catch (error) {
    console.error("Failed to retrieve battery level", error);
  }
};

fetchBatteryLevel();
Enter fullscreen mode Exit fullscreen mode

In this example, Turbo Modules make more efficient use of memory by loading BatteryStatusModule only when called, thus speeding up native calls.

3. New Configuration: Codegen and Native Components

Codegen is an automatic code generation tool that ensures type safety between JavaScript and native code. Working alongside TypeScript or Flow, it guarantees code accuracy and reduces the risk of errors.

  • Type Safety: Ensures strong type safety between JavaScript and native code.
  • Automatic Code Generation: Automates binding code, enhancing consistency and reducing repetitive tasks.

Setup and Configuration:

Run yarn react-native codegen in your project folder to create the necessary files and bindings.

You typically configure codegen in the react-native.config.js file.

yarn react-native codegen

Enter fullscreen mode Exit fullscreen mode
// react-native.config.js
module.exports = {
  dependencies: {
    // Native modules that require code generation
    'react-native-sample-module': {
      platforms: {
        ios: {
          // Path to the codegen file for iOS
          codegenConfig: {
            name: 'SampleModule',
            jsSrcsDir: './src',
          },
        },
        android: {
          // Path to the codegen file for Android
          codegenConfig: {
            name: 'SampleModule',
            jsSrcsDir: './src',
          },
        },
      },
    },
  },
};

Enter fullscreen mode Exit fullscreen mode

In this example, SampleModule is a native component that Codegen has automatically generated the necessary bridge connections and types for. This allows you to directly call SampleModule in JavaScript with full type safety and without needing to manually write bridge code.

// Importing the native module generated by Codegen
import { SampleModule } from 'react-native-sample-module';

// Directly calling a method on SampleModule, with type safety ensured by Codegen
const getSampleData = async () => {
  try {
    const data = await SampleModule.fetchData();
    console.log("Fetched Data:", data);
  } catch (error) {
    console.error("Failed to fetch data from SampleModule", error);
  }
};

// Invoking the function to get sample data
getSampleData();

Enter fullscreen mode Exit fullscreen mode

SampleModule.fetchData() is called directly, with Codegen ensuring type-safe access to the native module.
Any issues with fetching data from the native module are caught in the catch block for error handling.

import { TurboModule, TurboModuleRegistry } from 'react-native';

// Defining type safety for a native module using Codegen
interface BatteryModule extends TurboModule {
  getBatteryStatus(): Promise<string>;
}

export default TurboModuleRegistry.get<BatteryModule>('BatteryModule');

Enter fullscreen mode Exit fullscreen mode

Here, Codegen makes the getBatteryStatus function available with full type checking, reducing the risk of errors between JavaScript and native layers.

4. Enhanced Bridge

The new Bridge architecture optimizes interactions between native and JavaScript. By reducing dependency on the bridge, it enables critical tasks to be handled directly within native components.

  • Reduced Dependency: Some data processing occurs directly on the native side.
  • Native-Oriented Processing: Prioritizes handling of UI and data processing in native code, enhancing speed.

Advantages of the New Bridge Structure

With this innovation, applications can now communicate much faster with frequently used native functions.

Example:

import { DeviceEventEmitter } from 'react-native';

// Listening to a native event without needing the bridge
DeviceEventEmitter.addListener('BatteryLowWarning', (data) => {
  console.log("Battery at critical level:", data.level);
});

Enter fullscreen mode Exit fullscreen mode

In this example, a direct connection is established with the native side, and it responds immediately to the event.

Example:

import { NativeModules, DeviceEventEmitter } from 'react-native';
import React, { useEffect } from 'react';
import { Text } from 'react-native';

const { DeviceLocationModule } = NativeModules;

const LocationComponent = () => {
  // Accessing location information synchronously
  const initialLocation = DeviceLocationModule.getCurrentLocation();

  useEffect(() => {
    // Listening for location changes
    const locationSubscription = DeviceEventEmitter.addListener(
      'LocationChange',
      (newLocation) => {
        console.log(`Updated location: ${newLocation.latitude}, ${newLocation.longitude}`);
      }
    );

    return () => {
      locationSubscription.remove();
    };
  }, []);

  return <Text>Initial Location: {initialLocation.latitude}, {initialLocation.longitude}</Text>;
};

export default LocationComponent;
Enter fullscreen mode Exit fullscreen mode

Synchronous Access: The getCurrentLocation() call provides an instant response at app startup, speeding up the launch time.

Event Handling: The LocationChange event establishes a direct connection with the native side, reflecting location updates instantly without bridge delays.

This model enhances performance, especially for applications requiring real-time data streaming.

5. Enhanced Hermes Engine

Hermes is a JavaScript engine tailored for React Native and optimized for mobile. With the 0.76 updates, memory management and startup performance have improved, app size has been reduced, and startup time has been accelerated.

  • Smaller App Size: Hermes runtime becomes more compact.
  • Faster Startup: Starts faster with memory optimizations.
  • Advanced Setup: To enable Hermes, update your android/app/build.gradle file:
// Enabling Hermes
// android/app/build.gradle
project.ext.react = [
  enableHermes: true  // Enable Hermes for performance
]
Enter fullscreen mode Exit fullscreen mode

6. Concurrent Rendering and Suspense Support

Concurrent Rendering brings React’s priority-based rendering capabilities to React Native. This feature ensures that user interactions happen smoothly without interruptions.

  • Render on Demand: Heavy UI components are rendered in parts.
  • User Priority: Prioritizes user actions to provide a better experience.

Example:

import React, { useTransition } from 'react';

const Component = () => {
  const [isPending, startTransition] = useTransition();

  const handleLongProcess = () => {
    startTransition(() => {
      // Executing a heavy process concurrently
      performHeavyCalculations();
    });
  };

  return (
    <div>
      <button onClick={handleLongProcess}>Start Heavy Process</button>
      {isPending && <p>Processing...</p>}
    </div>
  );
};

export default Component;
Enter fullscreen mode Exit fullscreen mode

In this example, useTransition allows the heavy performHeavyCalculations function to run concurrently without blocking user interactions. The isPending flag helps indicate if the process is ongoing, providing feedback to the user, which improves the overall experience.

a. Suspense Support

import React, { Suspense } from 'react';

const DataComponent = React.lazy(() => import('./DataComponent'));

const App = () => {
  return (
    <div>
      <Suspense fallback={<p>Loading data...</p>}>
        <DataComponent />
      </Suspense>
    </div>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

In this example, Suspense is used to handle asynchronous data loading. The DataComponent is loaded lazily, and while it’s being fetched, a fallback message ("Loading data...") is displayed. This keeps the UI responsive and provides a smoother experience during data fetching.

7. New Event Loop and Performance Improvements

Goal: Synchronizing UI updates, making animations smoother, and providing faster responses to user inputs.

Details: The Event Loop is optimized to reduce the load on the main processor, allowing for quick responses to user actions. This provides a major advantage in animations and interactions that require high performance.

Example:

// Automatic batching with React 18
setCounter((c) => c + 1);
setCounter((c) => c + 1); // Increases with a single rendering
Enter fullscreen mode Exit fullscreen mode

a. Automatic Batching:

Normally, multiple state updates can trigger separate renders. However, with automatic batching, these updates are grouped together, avoiding unnecessary renders.

import React, { useState } from 'react';
import { Button, Text, View } from 'react-native';

function Counter() {
  const [count, setCount] = useState(0);
  const [anotherState, setAnotherState] = useState(false);

  const handleClick = () => {
    setCount(c => c + 1);
    setAnotherState(s => !s);
    // With automatic batching, both state updates happen within a single render.
  };

  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="Update" onPress={handleClick} />
    </View>
  );
}

export default Counter;
Enter fullscreen mode Exit fullscreen mode

In this example, the handleClick function includes two different state updates. With React Native 0.76’s automatic batching, these two updates are performed in a single render.

8. useLayoutEffect Support

useLayoutEffect is a React hook fully supported in React Native 0.76's New Architecture update. It provides synchronous access to layout information for DOM or native components. Unlike useEffect, which runs after rendering, useLayoutEffect executes during the commit phase. This allows you to adjust UI component positions immediately, as it works during rendering rather than post-render like useEffect.

Example:

If you want to place a tooltip in the correct position on the screen, you can use useLayoutEffect to position the element before rendering completes. This provides more precise placement, especially for animations or tooltips where positioning is crucial.

import React, { useLayoutEffect, useRef, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';

function TooltipExample() {
  const tooltipRef = useRef(null);
  const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });

  useLayoutEffect(() => {
    tooltipRef.current.measure((x, y, width, height, pageX, pageY) => {
      setTooltipPosition({ top: pageY + height, left: pageX });
    });
  }, []);

  return (
    <View ref={tooltipRef} style={styles.button}>
      <Text>Button</Text>
      <View style={[styles.tooltip, tooltipPosition]}>
        <Text>Tooltip content</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  button: { padding: 10, backgroundColor: 'lightblue' },
  tooltip: { position: 'absolute', backgroundColor: 'black', color: 'white', padding: 5 },
});

export default TooltipExample;
Enter fullscreen mode Exit fullscreen mode

In this example, useLayoutEffect ensures that the tooltip is positioned correctly right before rendering. By using measure() to get the element’s position, it is accurately placed on the screen.

9. Enhanced iOS and Android Support

  • iOS: The new architecture improves memory management and resource prioritization, making animations and UI interactions smoother on iOS.
  • Android: Integration of Turbo Modules and Fabric has been simplified on Android.

Conclusion

React Native 0.76 marks a significant step forward in the development process with performance improvements and new features. The New Architecture supports modern React 18 features like Suspense, Transitions, and Automatic Batching, enabling faster and smoother UIs. With Concurrent Rendering and Suspense Support, UI updates are quicker and more fluid, while Automatic Batching reduces unnecessary renders. useLayoutEffect simplifies synchronous component positioning. The removal of the old bridge and direct access to native modules via JavaScript Interface (JSI) and the new C++-based Native Module System enables type-safe access. Additionally, automatic type definitions via Codegen and an enhanced event loop make React Native more aligned with modern standards. Finally, temporary bridge compatibility allows for a gradual transition, providing flexibility during adaptation to the new architecture. These updates enhance developer productivity and improve user experience by making it faster and more seamless.

Reference: React Native 0.76 — New Architecture

Happy coding! 🚀

Top comments (0)