Skip to main content
Glama

get-memory-optimization

Debug memory leaks and optimize performance in React Native Expo apps by identifying common issues like useEffect cleanup, event listeners, timers, and closure leaks.

Instructions

Get memory optimization patterns. Call this when debugging memory leaks or performance issues. Covers useEffect cleanup (listeners, timers), closure memory leaks, React Native DevTools memory profiler, view flattening, R8 shrinking for Android, and a common memory leak sources checklist. Use topic to get a specific section only.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
topicNoGet a specific section only. Available: useeffect-cleanup, event-listeners, timers, closures, devtools-profiler, frame-budget, view-flattening, r8, checklist. Omit for full content.
compactNoIf true, returns rules only without code examples. Much shorter.

Implementation Reference

  • The `getMemoryOptimization` function, which acts as the handler for the `get-memory-optimization` tool, calling `resolvePattern` with the predefined pattern content.
    export const getMemoryOptimization = (topic?: string, compact?: boolean): string =>
      resolvePattern(pattern, topic, compact);
  • src/index.ts:238-256 (registration)
    Registration of the `get-memory-optimization` MCP tool in `src/index.ts`.
    server.tool(
      "get-memory-optimization",
      "Get memory optimization patterns. Call this when debugging memory leaks or performance issues. Covers useEffect cleanup (listeners, timers), closure memory leaks, React Native DevTools memory profiler, view flattening, R8 shrinking for Android, and a common memory leak sources checklist. Use `topic` to get a specific section only.",
      {
        topic: z
          .string()
          .optional()
          .describe(
            "Get a specific section only. Available: useeffect-cleanup, event-listeners, timers, closures, devtools-profiler, frame-budget, view-flattening, r8, checklist. Omit for full content."
          ),
        compact: z
          .boolean()
          .optional()
          .describe("If true, returns rules only without code examples. Much shorter."),
      },
      async ({ topic, compact }) => ({
        content: [{ type: "text", text: getMemoryOptimization(topic, compact) }],
      })
    );
  • Definition of the sections and content for the memory optimization tool.
    const sections: Record<string, string> = {
      'useeffect-cleanup': `## useEffect Cleanup — Always Return a Cleanup Function
    
    Every \`useEffect\` that creates a side effect must return a cleanup function:
    
    \`\`\`tsx
    // GOOD — cleanup prevents memory leaks
    useEffect(() => {
      const subscription = eventEmitter.addListener('event', handler);
      return () => subscription.remove();
    }, []);
    
    // BAD — listener leaks after component unmounts
    useEffect(() => {
      eventEmitter.addListener('event', handler); // never cleaned up
    }, []);
    \`\`\``,
    
      'event-listeners': `## Event Listener Cleanup
    
    \`\`\`tsx
    import { AppState, AppStateStatus, Keyboard } from 'react-native';
    
    const MyComponent = () => {
      useEffect(() => {
        const appStateSub = AppState.addEventListener('change', (state: AppStateStatus) => {
          if (state === 'active') syncData();
        });
    
        const keyboardShowSub = Keyboard.addListener('keyboardDidShow', handleKeyboardShow);
        const keyboardHideSub = Keyboard.addListener('keyboardDidHide', handleKeyboardHide);
    
        return () => {
          appStateSub.remove();
          keyboardShowSub.remove();
          keyboardHideSub.remove();
        };
      }, []);
    };
    \`\`\``,
    
      timers: `## Timer Cleanup
    
    \`\`\`tsx
    const PollingComponent = () => {
      useEffect(() => {
        const interval = setInterval(() => {
          fetchLatestData();
        }, 5000);
    
        return () => clearInterval(interval); // stop polling on unmount
      }, []);
    };
    
    const DelayedAction = () => {
      useEffect(() => {
        const timeout = setTimeout(() => {
          performAction();
        }, 2000);
    
        return () => clearTimeout(timeout); // cancel if component unmounts first
      }, []);
    };
    \`\`\``,
    
      closures: `## Closure Memory Leaks
    
    Closures capture references. Capture only the specific values you need:
    
    \`\`\`tsx
    // BAD — captures entire user object; keeps stale reference alive
    useEffect(() => {
      analytics.identify(user); // holds reference to entire user
    }, [user]);
    
    // GOOD — capture only the specific value needed
    const userId = user.id;
    useEffect(() => {
      analytics.identify(userId);
    }, [userId]);
    \`\`\``,
    
      'devtools-profiler': `## React Native DevTools Memory Profiler
    
    Use React Native DevTools to identify memory leaks:
    
    1. Open DevTools via Expo dev menu → "Open JS Debugger"
    2. Go to the **Memory** tab
    3. Take a **heap snapshot** — this is your baseline
    4. Reproduce the suspected leak (navigate to screen, perform actions, navigate away)
    5. Take another heap snapshot
    6. Compare: look for objects that should have been garbage collected
    
    **Key indicators:**
    - **Blue bars** — currently allocated memory
    - **Grey bars** — freed (garbage collected) memory
    - **Shallow size** — memory held by the object itself
    - **Retained size** — memory that would be freed if this object were collected
    
    **Red flag:** Objects from a screen still present in memory after navigating away.`,
    
      'frame-budget': `## 16ms Frame Budget (60 FPS)
    
    Each frame must render in 16ms for 60 FPS (8ms for 120 FPS ProMotion). Exceeding this causes dropped frames.
    
    - **JS thread**: business logic, state updates, React reconciliation
    - **UI thread**: layout, painting, touch handling
    - **Reanimated worklets**: run on UI thread, never block JS
    
    Keep heavy synchronous work off both threads.
    
    > Defer heavy work after animations with InteractionManager — see \`get-performance-patterns\` (topic: interaction-manager).`,
    
      'view-flattening': `## View Flattening
    
    React Native automatically flattens "layout-only" nodes in the view hierarchy (New Architecture). This reduces depth and improves render performance.
    
    When a child view gets unexpectedly flattened inside a native component expecting a specific number of children, use \`collapsable={false}\` to prevent flattening:
    
    \`\`\`tsx
    <MyNativeComponent>
      <Child1 collapsable={false} />
      <Child2 collapsable={false} />
      <Child3 collapsable={false} />
    </MyNativeComponent>
    \`\`\`
    
    **Debug view hierarchy:**
    - **iOS**: Xcode → "Debug View Hierarchy" button in debug toolbar
    - **Android**: Android Studio → View > Tool Windows > Layout Inspector`,
    
      r8: `## R8 Shrinking on Android
    
    R8 shrinks, optimizes, and obfuscates your APK. Enable in production:
    
    \`\`\`groovy
    // android/app/build.gradle
    def enableProguardInReleaseBuilds = true
    
    android {
      buildTypes {
        release {
          minifyEnabled true
          shrinkResources true
        }
      }
    }
    \`\`\`
    
    **Result:** Sample app shrank from 9.5 MB to 6.3 MB (33% reduction).
    
    Add ProGuard rules for libraries using reflection:
    \`\`\`
    # android/app/proguard-rules.pro
    -keep class io.invertase.firebase.** { *; }
    -dontwarn io.invertase.firebase.**
    \`\`\``,
    
      checklist: `## Common Memory Leak Sources Checklist
    
    - [ ] \`useEffect\` with event listeners missing cleanup
    - [ ] \`setInterval\`/\`setTimeout\` not cleared on unmount
    - [ ] Promises continuing after component unmount (use AbortController)
    - [ ] Closures capturing entire objects instead of specific values
    - [ ] Image cache growing unbounded (configure \`cachePolicy\` on expo-image)
    - [ ] Animation shared values holding references to large data structures
    - [ ] Native event subscriptions not removed (AppState, Keyboard, Dimensions)`,
    };
    
    // ─── Compact sections (rules only, no code) ─────────────────────────
    
    const compactSections: Record<string, string> = {
      'useeffect-cleanup': `## useEffect Cleanup
    - EVERY useEffect with side effects MUST return a cleanup function
    - Cleanup runs on unmount and before re-running the effect`,
    
      'event-listeners': `## Event Listeners
    - Always \`.remove()\` subscriptions in cleanup: AppState, Keyboard, Dimensions
    - Store subscription reference: \`const sub = addEventListener(...)\``,
    
      timers: `## Timers
    - \`clearInterval(id)\` and \`clearTimeout(id)\` in cleanup
    - Never leave timers running after unmount`,
    
      closures: `## Closures
    - Capture specific values, not entire objects: \`const userId = user.id\`
    - Prevents keeping stale references alive`,
    
      'devtools-profiler': `## Memory Profiler
    - React Native DevTools → Memory tab → Heap snapshots
    - Compare snapshots before/after navigation
    - Red flag: objects from unmounted screens still in memory`,
    
      'frame-budget': `## Frame Budget
    - 16ms per frame (60 FPS) / 8ms (120 FPS)
    - JS thread: logic, state, reconciliation
    - UI thread: layout, painting, touch
    - Defer heavy work with InteractionManager — see \`get-performance-patterns\``,
    
      'view-flattening': `## View Flattening
    - RN auto-flattens layout-only views (New Architecture)
    - Use \`collapsable={false}\` to prevent unwanted flattening
    - Debug: Xcode View Hierarchy (iOS), Layout Inspector (Android)`,
    
      r8: `## R8 (Android)
    - Enable \`minifyEnabled true\` + \`shrinkResources true\` for release builds
    - Add ProGuard keep rules for reflection-using libraries
    - ~33% APK size reduction`,
    
      checklist: `## Leak Checklist
    - [ ] useEffect cleanup for listeners/timers
    - [ ] AbortController for async operations
    - [ ] Specific value captures in closures
    - [ ] expo-image cachePolicy configured
    - [ ] Native subscriptions removed`,
    };
    
    // ─── Export ──────────────────────────────────────────────────────────
    
    const pattern: PatternSections = {
      title: 'Memory Optimization',
      sections,
      compactSections,
    };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ZaharGusyatin/react-native-expo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server