Threads in React Native
Threads in React Native
~9 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Unlike a pure web app, a React Native app runs distributed across MULTIPLE threads – understanding which code runs on which thread also explains why some animations stutter and others don't (see the "Fade in/out Animation" chapter).
1. Description
The classic (bridge) architecture distinguishes three main threads: the JS THREAD runs your React/JavaScript code (a SINGLE thread, no multithreading within JS itself!), the NATIVE/UI THREAD draws the actual native views (Android: main thread, iOS: main thread), the SHADOW THREAD computes the flexbox layout (Yoga engine) in the background. The newer JSI architecture (see the "Bridging" chapter) blurs this separation somewhat, but the basic principle remains.
2. Short example
// Blocks the JS thread for 3 seconds -
// all touch events AND state updates are delayed during this time:
function blockJsThread() {
const end = Date.now() + 3000;
while (Date.now() < end) {} // deliberately bad example!
}3. Complete project: a responsiveness demo
npx create-expo-app threads-demo
cd threads-demoimport { useRef, useState } from 'react';
import { View, Text, Button, Animated, StyleSheet } from 'react-native';
export default function App() {
const [count, setCount] = useState(0);
const rotation = useRef(new Animated.Value(0)).current;
// Keeps running as long as the component exists - on the NATIVE thread thanks to useNativeDriver
function startRotation() {
rotation.setValue(0);
Animated.loop(
Animated.timing(rotation, {
toValue: 1,
duration: 1500,
useNativeDriver: true,
})
).start();
}
// Deliberately blocks the JS thread
function blockJsThread() {
const end = Date.now() + 2000;
while (Date.now() < end) {}
}
const rotationStyle = {
transform: [{
rotate: rotation.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'] }),
}],
};
return (
<View style={styles.container}>
<Animated.View style={[styles.box, rotationStyle]} />
<Text style={styles.countText}>Count: {count}</Text>
<Button title="Start rotation (native animation)" onPress={startRotation} />
<View style={styles.spacing}>
<Button title="Increment count (JS thread)" onPress={() => setCount((c) => c + 1)} />
</View>
<View style={styles.spacing}>
<Button title="Block JS thread for 2s" onPress={blockJsThread} color="#dc2626" />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', paddingTop: 80 },
box: { width: 60, height: 60, backgroundColor: '#2563eb', marginBottom: 24 },
countText: { fontSize: 18, marginBottom: 24 },
spacing: { marginTop: 12 },
});4. Explanation
- "Start rotation" uses
useNativeDriver: true– the animation runs entirely on the NATIVE thread; it keeps spinning even while "Block JS thread for 2s" is pressed. - "Increment count", on the other hand, runs on the JS thread – during the 2-second block, the button does NOT respond, because the JS event loop is blocked and the
onPressevent can only be processed afterward. - This exact difference explains why
useNativeDriver: truematters so much: animations should stay smooth even while the JS thread is busy with expensive work (network parsing, complex state updates). - For GENUINELY compute-heavy work (image processing, complex algorithms), there are additional libraries like
react-native-worklets-corethat run code on their own, separate thread – beyond the scope of this topic.