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

