KlarkLabs
Back to blog
Mobile
20 January 2026
4 min read

Optimizing React Native Performance

Advanced techniques to improve the performance of your React Native applications in production.

React NativePerformanceMobile

Performance is often the Achilles' heel of React Native applications. Between unnecessary re-renders, janky animations, and slow startup times, the issues are plentiful. Here are the techniques we apply systematically in production to achieve near-native fluidity.

Understanding the Threading Model

React Native uses three main threads: the JS thread, the UI thread (native), and the Shadow thread (layout). Most performance issues stem from a saturated JS thread blocking rendering. With the New Architecture (Fabric + JSI), this model is evolving toward synchronous rendering, but understanding the fundamentals remains essential.

1. Avoiding Re-renders with memo and useCallback

The most common issue is the recreation of functions and objects on every render, which invalidates downstream memoizations and causes cascading re-renders.

// Bad: new reference on every render
const MyList = ({ userId }: { userId: string }) => {
  const handlePress = (item: Item) => {
    console.log(item.id, userId);
  };
 
  return <FlatList data={items} renderItem={({ item }) => (
    <ItemCard item={item} onPress={handlePress} />
  )} />;
};
 
// Correct: stable reference with useCallback
const MyList = ({ userId }: { userId: string }) => {
  const handlePress = useCallback((item: Item) => {
    console.log(item.id, userId);
  }, [userId]);
 
  const renderItem = useCallback(({ item }: { item: Item }) => (
    <ItemCard item={item} onPress={handlePress} />
  ), [handlePress]);
 
  return <FlatList data={items} renderItem={renderItem} />;
};

2. Optimizing FlatList with getItemLayout

If your list items have a fixed height, getItemLayout prevents React Native from measuring each item during scrolling, dramatically improving performance on long lists.

const ITEM_HEIGHT = 80;
const SEPARATOR_HEIGHT = 1;
 
<FlatList
  data={data}
  keyExtractor={(item) => item.id}
  getItemLayout={(_, index) => ({
    length: ITEM_HEIGHT,
    offset: (ITEM_HEIGHT + SEPARATOR_HEIGHT) * index,
    index,
  })}
  initialNumToRender={10}
  maxToRenderPerBatch={10}
  windowSize={5}
  removeClippedSubviews={true}
  renderItem={renderItem}
/>

3. Animations with Reanimated 3

Always run animations on the UI thread, never on the JS thread. Reanimated 3 with worklets enables exactly that, ensuring 60fps animations even when the JS thread is busy.

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  runOnJS,
} from "react-native-reanimated";
 
const PressableCard = ({ onPress }: { onPress: () => void }) => {
  const scale = useSharedValue(1);
 
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));
 
  const handlePressIn = () => {
    scale.value = withSpring(0.95, { damping: 15 });
  };
 
  const handlePressOut = () => {
    scale.value = withSpring(1, { damping: 15 }, (finished) => {
      if (finished) runOnJS(onPress)();
    });
  };
 
  return (
    <Animated.View style={animatedStyle}>
      <Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
        {/* content */}
      </Pressable>
    </Animated.View>
  );
};

4. Reducing Startup Time (TTI)

Time To Interactive (TTI) is the most user-visible metric. Several strategies help reduce it:

Lazy loading screens: Only load the screens visible at startup.

// navigation/RootNavigator.tsx
const HomeScreen = lazy(() => import("../screens/HomeScreen"));
const ProfileScreen = lazy(() => import("../screens/ProfileScreen"));
const SettingsScreen = lazy(() => import("../screens/SettingsScreen"));

Hermes: Enable the Hermes JavaScript engine (on by default since RN 0.70), which reduces TTI by 30 to 40% thanks to pre-compiled bytecode that starts executing before the full bundle is parsed.

5. Profiling with Flipper and Flashlight

Never optimize blindly. Use Flipper with the React DevTools plugin to identify components that re-render unnecessarily. For deeper analysis, Flashlight measures an overall performance score for your application on a real Android device.

# Install Flashlight CLI
npm install -g @perf-tools/flashlight
 
# Measure performance over a user journey
flashlight measure --apk ./app-release.apk --test ./e2e/homeFlow.js

Observed Results

Applying these techniques to an e-commerce app with 500K active users:

  • TTI reduced from 2.8s to 1.1s
  • 73% fewer unnecessary re-renders
  • Average FPS on scrollable lists improved from 42 to 58 FPS

Conclusion

React Native optimization is a continuous effort that requires measuring before optimizing. Start by identifying your actual bottlenecks with profiling tools, apply targeted fixes, then measure again. The most significant gains typically come from poorly configured FlatLists and animations running on the wrong thread.

React NativePerformanceMobile
Klark Labs
Technology Studio