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.