← All shorts
33React Native 0.72+2 min read

Fabric: The New Architecture Renderer in Plain Terms

Fabric replaces the old UIManager bridge with a synchronous, thread-safe renderer built on JSI.

The old renderer kept two trees in sync across an async bridge: the one JS computed, and the one the platform actually drew. Every measure, layout, and commit got serialized to JSON and sat in a queue until the next batch flush.

That queue has a cost you have seen. Rotate a device and you get one stale frame before the layout catches up. Drag something and it lands a beat behind your finger.

#The old way

import { UIManager, findNodeHandle, View } from 'react-native';

function measureButton(ref: React.RefObject<View>) {
  const node = findNodeHandle(ref.current);
  if (node == null) return;
  UIManager.measure(node, (x, y, width, height, pageX, pageY) => {
    console.log(width, height);
  });
}

The call crosses the bridge as a serialized message. Its answer arrives on a later batch, so the width and height you log are always at least one frame stale.

#The new way

import { useWindowDimensions, View, Text, StyleSheet } from 'react-native';

export function OrientationAwareCard() {
  const { width, height } = useWindowDimensions();
  const isLandscape = width > height;

  return (
    <View style={[styles.card, isLandscape && styles.cardWide]}>
      <Text style={styles.label}>
        {isLandscape ? 'Landscape' : 'Portrait'} — {Math.round(width)}x{Math.round(height)}
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  card: {
    padding: 16,
    borderRadius: 12,
    backgroundColor: '#1c1c1e',
  },
  cardWide: {
    flexDirection: 'row',
  },
  label: {
    color: '#fff',
    fontSize: 16,
  },
});

The component code did not change. Everything under it did. Fabric backs every host component with a C++ ShadowNode and commits the shadow tree synchronously, so useWindowDimensions reports the new size in the same frame instead of one frame late.

#Why it matters

  • Rotation and keyboard show/hide stop flashing a stale frame, because layout commits are synchronous.
  • The C++ core makes host components thread-safe. Later concurrent-rendering features build on that.
  • An interop layer keeps old-architecture native modules working, so you can migrate one piece at a time.

#Gotcha

Every native view manager that touches the screen has to ship a Fabric component descriptor or run through the interop layer. A third-party native view built only for the old renderer can crash on mount. Or render nothing at all, silently, until its maintainer ships a Fabric-compatible release.

react-nativeperformance

Related shorts