# How React Native Manages State and Props Across the Bridge: A Deep Dive into the Architecture

> Discover how React Native manages state and props across the bridge. Learn about the architecture for efficient updates and optimized performance in your apps.

- Repository: [Meta/react-native](https://github.com/facebook/react-native)
- Tags: deep-dive
- Published: 2026-02-25

---

**React Native maintains state entirely within the JavaScript runtime while props are diffed and serialized across the bridge via `UIManager.updateView`, with the reconciler in [`ReactNativeRenderer-dev.js`](https://github.com/facebook/react-native/blob/main/ReactNativeRenderer-dev.js) computing minimal update payloads to optimize performance.**

React Native's architecture relies on a bridge to communicate between JavaScript and native platforms. Understanding how state and props traverse this boundary is essential for optimizing performance in the `facebook/react-native` repository. While state remains a pure JavaScript concern managed by React's Fiber scheduler, props must be serialized and batched across the bridge to update native views.

## Where State Lives in React Native

State in React Native is a purely JavaScript-side concept. When you call `this.setState` or `useState` dispatchers, updates are queued in React's **Fiber scheduler** without any immediate interaction with native code.

The state object lives on the Fiber node within the JavaScript runtime. It persists across renders and only influences the native side indirectly when the reconciler determines that new props must be pushed across the bridge. This separation ensures that frequent state updates do not incur serialization overhead unless they result in visible prop changes.

## How Props Cross the Bridge

While state remains in JavaScript, props must traverse the bridge to configure native views. The journey from JavaScript state change to native view update follows a precise pipeline implemented in the renderer.

### The Render and Reconcile Phase

When state changes trigger a re-render, React's reconciler walks the Fiber tree and computes the difference between previous and new props for each host component (such as `<View>` or `<Text>`). This diffing logic lives in `diffProperties` inside [`ReactNativeRenderer-dev.js`](https://github.com/facebook/react-native/blob/main/ReactNativeRenderer-dev.js) (and its production/Fabric equivalents).

According to the source code at [`packages/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js`](https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js), the `diffProperties` function (around line 1567) compares the previous and next prop objects to identify which values have actually changed.

### Building the Update Payload

The `diffProperties` function returns an **update payload**—a flattened object containing only the changed native props such as style, text content, or layout attributes. This minimal payload design reduces the serialization cost and bridge traffic.

The payload structure typically follows a key-value format where indices alternate between property keys and values, optimized for the native side to parse efficiently.

### Committing Updates via UIManager

Once the payload is ready, the renderer calls `commitUpdate` (found around line 2129 in [`ReactNativeRenderer-dev.js`](https://github.com/facebook/react-native/blob/main/ReactNativeRenderer-dev.js)). This function writes the new payload into the internal `instanceProps` map and then invokes `UIManager.updateView` (or `FabricUIManager.updateView` for Fabric).

This represents the first actual JavaScript-to-native crossing in the update cycle. The `UIManager.updateView` method is defined in [`packages/react-native/Libraries/ReactNative/UIManager.js`](https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/ReactNative/UIManager.js) and serves as the JavaScript façade that forwards calls to the native implementation.

## The Bridge Serialization Process

The actual crossing from JavaScript to native code happens through the **BatchedBridge**, a thin wrapper around `MessageQueue` defined in [`packages/react-native/Libraries/BatchedBridge/BatchedBridge.js`](https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/BatchedBridge/BatchedBridge.js) and [`MessageQueue.js`](https://github.com/facebook/react-native/blob/main/MessageQueue.js).

When `UIManager.updateView` is called, the request is queued in the BatchedBridge. At the end of the JavaScript execution tick, all queued native calls are serialized into a JSON payload and shipped across the bridge in a single batch. This batching mechanism minimizes the overhead of crossing the JavaScript-native boundary.

On the native side, platform-specific modules receive the batched calls. On Android, [`UIManagerModule.java`](https://github.com/facebook/react-native/blob/main/UIManagerModule.java) (located in [`packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java`](https://github.com/facebook/react-native/blob/main/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java)) processes `updateView` calls, while iOS uses `RCTUIManager.m` (found in `ReactCommon/cxx/react/renderer/uimanager/RCTUIManager.mm` for modern architectures).

## Fabric vs. Paper Architecture

React Native supports two rendering architectures: **Paper** (the legacy bridge) and **Fabric** (the new architecture).

The renderer determines which path to use by checking `isFabricReactTag`. Even-numbered tags belong to Fabric, while odd-numbered tags use Paper. Both paths eventually call a native `updateView` method, but the underlying native modules differ:

- **Paper**: Uses the traditional `UIManager` module with asynchronous batched bridge calls.
- **Fabric**: Uses `FabricUIManager` with a C++ renderer and JSI (JavaScript Interface) for synchronous communication, reducing latency.

The `FabricUIManager` implementation is exposed in [`packages/react-native/Libraries/Renderer/implementations/FabricUIManager.js`](https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Renderer/implementations/FabricUIManager.js).

## Bypassing the Reconciler with setNativeProps

For performance-critical animations that require bypassing React's diffing overhead, React Native exposes `setNativeProps`. This method allows direct manipulation of native view properties without triggering a full render pass.

The implementation resides in [`ReactNativeRenderer-dev.js`](https://github.com/facebook/react-native/blob/main/ReactNativeRenderer-dev.js) around line 17340. When called, `_proto.setNativeProps` builds an update payload and immediately invokes `UIManager.updateView`, skipping the Fiber reconciler entirely.

Use this sparingly, as it breaks the declarative React model and can cause inconsistencies if the component later re-renders with conflicting props.

### Example: Direct Native Updates

```javascript
import React, {useRef} from 'react';
import {View, Text} from 'react-native';

export default function AnimatedBox() {
  const boxRef = useRef(null);

  // Bypasses React diff; calls UIManager.updateView directly
  const shrink = () => {
    boxRef.current?.setNativeProps({
      style: {transform: [{scale: 0.5}]}
    });
  };

  return (
    <View ref={boxRef} style={{width: 100, height: 100, backgroundColor: 'tomato'}}>
      <Text onPress={shrink}>Tap to shrink</Text>
    </View>
  );
}

```

### Example: State-Driven Updates

```javascript
import React, {useState} from 'react';
import {View, Text, StyleSheet} from 'react-native';

export default function Counter() {
  const [count, setCount] = useState(0);

  // Each tap triggers setState → React schedules a render →
  // diffProperties produces {text: '1'} → UIManager.updateView is called
  return (
    <View style={styles.box}>
      <Text>{count}</Text>
      <Text style={styles.button} onPress={() => setCount(c => c + 1)}>
        Increment
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  box: {padding: 10, backgroundColor: '#eee'},
  button: {color: 'blue'},
});

```

## Summary

- **State** remains entirely within the JavaScript runtime on Fiber nodes and never crosses the bridge directly.
- **Props** are diffed by `diffProperties` in [`ReactNativeRenderer-dev.js`](https://github.com/facebook/react-native/blob/main/ReactNativeRenderer-dev.js) to produce minimal update payloads.
- The **UIManager** module ([`UIManager.js`](https://github.com/facebook/react-native/blob/main/UIManager.js)) serves as the gateway for prop updates crossing into native code.
- **BatchedBridge** queues and serializes calls at the end of each JavaScript tick to minimize bridge overhead.
- **Fabric** (even tags) and **Paper** (odd tags) use different native modules but follow the same conceptual flow.
- **`setNativeProps`** provides an escape hatch for direct native updates, bypassing React's reconciler entirely.

## Frequently Asked Questions

### Does React Native sync state to the native side?

No. State is a pure JavaScript concept managed by React's Fiber scheduler within the JS runtime. When state changes trigger a re-render, only the resulting prop differences are serialized across the bridge via `UIManager.updateView`. The native side never sees the actual state objects, only the prop payloads derived from them.

### What is the difference between Paper and Fabric in bridge communication?

Paper (legacy) uses the traditional bridge with asynchronous batched calls through [`UIManager.js`](https://github.com/facebook/react-native/blob/main/UIManager.js), while Fabric (new architecture) uses `FabricUIManager` with the C++ renderer and JSI (JavaScript Interface) for synchronous communication. The renderer distinguishes them by tag parity: even-numbered React tags use Fabric, odd tags use Paper.

### How does setNativeProps improve performance?

`setNativeProps` bypasses React's Fiber reconciler and `diffProperties` entirely. It directly calls `UIManager.updateView` with the supplied prop changes, eliminating the overhead of render passes and diffing calculations. This is ideal for high-frequency animations but risks prop synchronization issues if React later reconciles conflicting values.

### Where is the bridge batching logic implemented?

The batching logic resides in [`packages/react-native/Libraries/BatchedBridge/BatchedBridge.js`](https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/BatchedBridge/BatchedBridge.js) and [`MessageQueue.js`](https://github.com/facebook/react-native/blob/main/MessageQueue.js). These modules queue native method calls during the JavaScript execution tick and flush them in a single serialized batch at the end of the tick, minimizing the cost of crossing the JavaScript-native boundary.