Animations and gestures stutter in the React Native app
“Sheets, swipes and transitions are smooth on an empty screen but stutter as soon as data loads or the screen re-renders, and it is worse on mid-range Android.”
THE PRINCIPLE
An animation is only as smooth as the busiest thread that computes it. Put per-frame work on the UI thread and keep React renders out of the frame loop.
FIRST MOVES
- Measure in a release build (or the Android debugOptimized build type, RN 0.81+ and Expo SDK 54+): dev mode slows the JS thread sharply. Turn on the Perf Monitor and see which rate drops, JS or UI.
- If JS drops: profile renders with React Native DevTools, stop setState-per-frame patterns, memoize heavy subtrees, and defer non-urgent work until after the interaction.
- Drive animations with Reanimated shared values and worklets (useSharedValue, useAnimatedStyle, withSpring) and gestures with react-native-gesture-handler, so frames don't wait for JS. With core Animated, set useNativeDriver: true where the property supports it.
- Animate transform and opacity instead of width, height, top or margin, which force layout every frame; avoid reading shared values from the JS thread, which blocks until the UI thread answers.
- If UI drops: count simultaneously animated views (Reanimated suggests ~100 on low-end Android), check overdraw and huge images, and review Reanimated's New Architecture performance flags for your RN version.
TOOLS: THEN → NOW
Animated with useNativeDriver: false and setState-driven animationReanimated 4 worklets on the UI thread (New Architecture only), or Animated with the native driver
PanResponder for gesturesreact-native-gesture-handler 3 hooks (usePanGesture), or memoized Gesture builders on v2
Flipper performance pluginsReact Native DevTools profiler, Perf Monitor, Perfetto and Xcode Instruments
PATTERN SNAPSHOT
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated";
import { GestureDetector, usePanGesture } from "react-native-gesture-handler"; // v3
function Draggable() {
const x = useSharedValue(0);
// With Reanimated installed, callbacks run as UI-thread worklets: no React render.
const pan = usePanGesture({
onUpdate: (e) => { x.value = e.translationX; },
onDeactivate: () => { x.value = withSpring(0); },
});
const style = useAnimatedStyle(() => ({ transform: [{ translateX: x.value }] }));
return <GestureDetector gesture={pan}><Animated.View style={[{ width: 80, height: 80 }, style]} /></GestureDetector>;
}CLOSE THE AI. EXPLAIN THIS.
The Perf Monitor shows UI at 60 fps and JS at 15 fps while a drag stutters. Which thread is computing the animation, and what is the fix?HOW IT WORKS UNDERNEATH
RELATED SYMPTOMS
SOURCES
Guide reviewed