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} /> 

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]); 

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 /> 

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`; 

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) })); 

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+) 

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