DEV Community

Cover image for How to Fix React Native Lag, Memory Leaks & Hit 60 FPS in Production
Inayat Ali
Inayat Ali

Posted on Edited on Originally published at innunext.com

How to Fix React Native Lag, Memory Leaks & Hit 60 FPS in Production

How to Fix React Native Lag, Memory Leaks & Hit 60 FPS in Production

After auditing 12+ production React Native codebases, 80% of "the app feels slow" complaints trace back to the same 3 root causes.

Here's the exact fix for each.


1. Stop Re-Rendering Everything on Every State Tick

The most common frame drop: anonymous inline callbacks creating new function references on every parent render.

// ❌ New function on every render → child re-renders every time
<List onPress={() => navigate("Detail", { id })} />

// ✅ Stable reference → no unnecessary re-renders
const handlePress = useCallback(() => navigate("Detail", { id }), [id]);
<List onPress={handlePress} />
Enter fullscreen mode Exit fullscreen mode

Also swap AsyncStorage for MMKV on mount reads — it's C++ JSI, synchronous, and 10x faster for cold-start hydration.


2. Memory Leaks — The Silent Killer

Apps using WebSockets, Supabase listeners, background GPS, or BLE leak memory every time a screen unmounts without cleaning up native subscriptions. Over 20 minutes: Android kills the process, iOS jetsams it.

useEffect(() => {
  const controller = new AbortController();
  const unsubscribe = socket.subscribe("channel", (payload) => {
    if (!controller.signal.aborted) setState(payload);
  });
  return () => {
    controller.abort();
    unsubscribe(); // No zombie listeners, no RAM retention
  };
}, [channelId]);
Enter fullscreen mode Exit fullscreen mode

Rule: Every useEffect that opens a subscription, timer, or native listener must return a cleanup function. No exceptions.


3. FlatList → FlashList + Fix Your Images

FlatList doesn't recycle views during fast scrolling → blank cells → perceived lag.

// Drop-in replacement, same API
import { FlashList } from "@shopify/flash-list";

<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={72} // Measure your actual item height
/>
Enter fullscreen mode Exit fullscreen mode

And never decode a 4000×3000px upload inside an 80×80px avatar:

// ✅ Request the right size from your CDN
const thumb = `${user.avatarUrl}?w=160&h=160&fit=crop`;
Enter fullscreen mode Exit fullscreen mode

Use expo-image over the built-in Image for hardware-decoded caching.


4. Move Animations to the UI Thread

Any Animated.Value on the JS thread drops frames the moment a network call or navigation fires.

// ❌ JS thread — stutters during API calls
Animated.timing(opacity, { toValue: 1, useNativeDriver: true }).start();

// ✅ Reanimated worklet — C++ UI thread, frame-perfect
const opacity = useSharedValue(0);
const style = useAnimatedStyle(() => ({ opacity: withTiming(opacity.value) }));
Enter fullscreen mode Exit fullscreen mode

Diagnostic Order

Profile on a real mid-range Android device in release mode — never Metro dev mode (timings are 3–5x slower).

1. React DevTools Profiler → find heaviest re-render tree
2. Audit all useEffects for missing cleanup
3. FlatList → FlashList on any list > 50 items
4. CDN-resize images to display size
5. Migrate animations to Reanimated worklets
6. Enable New Architecture in app.json (Expo SDK 51+)
Enter fullscreen mode Exit fullscreen mode

Performance bugs in React Native are diagnosable, not mysterious. These 4 patterns cover 90% of real-world production cases.

If your existing app is lagging or crashing on Android, I write about more patterns like this at innunext.com and do deep-dive production audits.


Tags: reactnative javascript mobile performance typescript

Top comments (0)