← Back to the library
MobileDeep dive · 9 min

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

SOURCES

Guide reviewed