Skip to main content
Glama

get-performance-patterns

Optimize React Native Expo app performance by implementing patterns for lists, images, bundle size, animations, and concurrent rendering.

Instructions

Get performance optimization patterns. Call this when optimizing lists, images, bundle size, or animations. Covers LegendList/FlashList/FlatList, image optimization, tree-shaking, barrel exports, Concurrent React (useDeferredValue, useTransition), InteractionManager, and Reanimated worklets. Use topic to get a specific section only.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
topicNoGet a specific section only. Available: lists, images, tree-shaking, barrel-exports, bundle, concurrent, interaction-manager, reanimated, checklist. Omit for full content.
compactNoIf true, returns rules only without code examples. Much shorter.

Implementation Reference

  • The function `getPerformancePatterns` serves as the handler for the tool "get-performance-patterns", delegating to `resolvePattern`.
    export const getPerformancePatterns = (topic?: string, compact?: boolean): string =>
      resolvePattern(pattern, topic, compact);
  • src/index.ts:169-187 (registration)
    The "get-performance-patterns" tool is registered in `src/index.ts` using `server.tool` and invokes `getPerformancePatterns` from `src/content/patterns/performance-patterns.ts`.
    server.tool(
      "get-performance-patterns",
      "Get performance optimization patterns. Call this when optimizing lists, images, bundle size, or animations. Covers LegendList/FlashList/FlatList, image optimization, tree-shaking, barrel exports, Concurrent React (useDeferredValue, useTransition), InteractionManager, and Reanimated worklets. Use `topic` to get a specific section only.",
      {
        topic: z
          .string()
          .optional()
          .describe(
            "Get a specific section only. Available: lists, images, tree-shaking, barrel-exports, bundle, concurrent, interaction-manager, reanimated, 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: getPerformancePatterns(topic, compact) }],
      })
    );
  • The input schema for the tool is defined using Zod, expecting optional `topic` and `compact` parameters.
    {
      topic: z
        .string()
        .optional()
        .describe(
          "Get a specific section only. Available: lists, images, tree-shaking, barrel-exports, bundle, concurrent, interaction-manager, reanimated, checklist. Omit for full content."
        ),
      compact: z
        .boolean()
        .optional()
        .describe("If true, returns rules only without code examples. Much shorter."),
    },
  • `performance-patterns.ts` utilizes the `resolvePattern` helper and `PatternSections` type definition to organize and serve content sections.
    import { PatternSections, resolvePattern } from './pattern-helper';
    
    // ─── Full sections (with code examples) ─────────────────────────────
    
    const sections: Record<string, string> = {
      lists: `## List Components
    
    For any list — use a performant list component. NEVER \`ScrollView\` + \`.map()\`.
    
    ### LegendList — Recommended
    
    LegendList is the modern choice for React Native (New Architecture, JavaScript-only, no native modules needed).
    
    \`\`\`tsx
    import { LegendList } from '@legendapp/list';
    
    const ITEM_HEIGHT = 80;
    
    <LegendList
      data={items}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <Card {...item} />}
      estimatedItemSize={ITEM_HEIGHT}
      recycleItems   // enables item recycling for better performance
    />
    \`\`\`
    
    **estimatedItemSize** is required. If items have variable heights — use the average value.
    
    ### FlashList — Battle-tested Alternative
    
    FlashList from Shopify is a drop-in replacement for FlatList. Uses item recycling.
    
    \`\`\`tsx
    import { FlashList } from '@shopify/flash-list';
    
    <FlashList
      data={items}
      renderItem={({ item }) => <Card {...item} />}
      estimatedItemSize={ITEM_HEIGHT}
      keyExtractor={(item) => item.id}
    />
    \`\`\`
    
    FlashList vs FlatList (Flashlist benchmark):
    - FlashList: score 68/100, ~56 FPS stable
    - FlatList: score 25/100, visible FPS drops
    
    ### FlatList — Fallback
    
    Use when LegendList/FlashList are not an option. Key optimization: always provide \`getItemLayout\` for fixed-height items.
    
    \`\`\`tsx
    <FlatList
      data={items}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <Card {...item} />}
      getItemLayout={(_, index) => ({
        length: ITEM_HEIGHT,
        offset: ITEM_HEIGHT * index,
        index,
      })}
      windowSize={10}
      maxToRenderPerBatch={10}
      removeClippedSubviews={true}
      initialNumToRender={10}
    />
    \`\`\`
    
    > Always \`React.memo\` list item components — see \`get-component-patterns\` (topic: react-memo).`,
    
      images: `## Image Optimization
    
    ### WebP Format
    
    ALWAYS use WebP instead of PNG/JPG:
    - 2-3x smaller file size
    - Transparency support
    - Animation support
    
    > Use \`expo-image\` for all images with \`cachePolicy="memory-disk"\` — see \`get-component-patterns\` (topic: expo-image).`,
    
      'tree-shaking': `## Tree-Shaking Imports
    
    \`\`\`tsx
    // GOOD — tree-shakeable (direct submodule imports)
    import format from 'date-fns/format';
    import addDays from 'date-fns/addDays';
    
    // BAD — pulls in the entire package
    import { format, addDays } from 'date-fns';
    \`\`\``,
    
      'barrel-exports': `## Avoid Barrel Exports
    
    Barrel exports (\`index.ts\` with re-exports) prevent tree-shaking and increase bundle size.
    
    \`\`\`tsx
    // BAD — barrel export in components/index.ts
    // Importing one component pulls in all of them
    export { Button } from './Button';
    export { Card } from './Card';
    export { Avatar } from './Avatar';
    
    // GOOD — direct imports
    import { Button } from '@/components/ui/Button';
    import { Card } from '@/components/ui/Card';
    \`\`\`
    
    Use \`eslint-plugin-no-barrel-imports\` to enforce this rule.`,
    
      bundle: `## Bundle Size Analysis
    
    \`\`\`bash
    # Analyze bundle with Expo Atlas (recommended)
    EXPO_UNSTABLE_ATLAS=true npx expo start --no-dev
    npx expo-atlas
    
    # Source map explorer
    npx expo export --dump-sourcemap
    npx source-map-explorer output.js
    \`\`\``,
    
      concurrent: `## Concurrent React Features
    
    ### useDeferredValue
    
    For heavy computations — defer rendering of non-critical components:
    
    \`\`\`tsx
    import { useDeferredValue, useState } from 'react';
    
    function SearchScreen() {
      const [query, setQuery] = useState('');
      const deferredQuery = useDeferredValue(query);
      const isStale = query !== deferredQuery;
    
      return (
        <View>
          <TextInput value={query} onChangeText={setQuery} placeholder="Search..." />
          <View style={[isStale && { opacity: 0.8 }]}>
            <SearchResults query={deferredQuery} />
          </View>
        </View>
      );
    }
    \`\`\`
    
    > Wrap computation-heavy components in \`React.memo()\` when passing deferred values.
    
    ### useTransition
    
    For non-critical state updates — mark them as low priority:
    
    \`\`\`tsx
    import { useState, useTransition } from 'react';
    
    function FilterScreen() {
      const [filter, setFilter] = useState('all');
      const [results, setResults] = useState([]);
      const [isPending, startTransition] = useTransition();
    
      const handleFilterChange = (newFilter: string) => {
        setFilter(newFilter); // High priority — updates UI immediately
        startTransition(() => {
          setResults(filterData(newFilter)); // Low priority — doesn't block UI
        });
      };
    
      return (
        <View>
          <FilterBar filter={filter} onChange={handleFilterChange} />
          {isPending ? <ActivityIndicator /> : <ResultsList data={results} />}
        </View>
      );
    }
    \`\`\`
    
    **How to choose**: \`useDeferredValue\` — for a single value. \`useTransition\` — for entire state updates.`,
    
      'interaction-manager': `## InteractionManager
    
    Defer heavy operations until after animations complete:
    
    \`\`\`tsx
    import { InteractionManager } from 'react-native';
    
    useEffect(() => {
      const task = InteractionManager.runAfterInteractions(() => {
        // Heavy operation after transition animation finishes
        loadHeavyData();
      });
    
      return () => task.cancel();
    }, []);
    \`\`\``,
    
      reanimated: `## Animations — Reanimated Worklets
    
    For 60+ FPS animations — use react-native-reanimated worklets that run on the UI thread:
    
    \`\`\`tsx
    import Animated, {
      useSharedValue,
      useAnimatedStyle,
      withSpring,
    } from 'react-native-reanimated';
    
    function AnimatedBox() {
      const offset = useSharedValue(0);
    
      const animatedStyle = useAnimatedStyle(() => ({
        transform: [{ translateX: withSpring(offset.value) }],
      }));
    
      return (
        <Animated.View style={animatedStyle} className="w-20 h-20 bg-primary rounded-lg" />
      );
    }
    \`\`\`
    
    Worklets run on the UI thread — the JS thread is not blocked. Critical for 60+ FPS animations.
    
    **16 ms budget per frame** (60 FPS) / **8 ms for 120 FPS**. Functions that run longer than this budget will drop frames.`,
    
      checklist: `## Performance Checklist
    
    - [ ] Lists: LegendList / FlashList (NOT ScrollView + map)
    - [ ] estimatedItemSize for LegendList/FlashList; getItemLayout for FlatList
    - [ ] React.memo for list items
    - [ ] expo-image instead of Image (caching, blurhash)
    - [ ] WebP format for images
    - [ ] Tree-shaking imports (direct submodule imports)
    - [ ] Avoid barrel exports
    - [ ] React Compiler (if React 19+)
    - [ ] Reanimated for animations (UI thread worklets)
    - [ ] InteractionManager for heavy operations after transition
    - [ ] useDeferredValue / useTransition for non-critical updates`,
    };
    
    // ─── Compact sections (rules only, no code) ─────────────────────────
    
    const compactSections: Record<string, string> = {
      lists: `## Lists
    - NEVER use \`ScrollView\` + \`.map()\` for lists
    - **LegendList** (recommended): JS-only, New Architecture, \`recycleItems\` prop
    - **FlashList** (alternative): Shopify, drop-in FlatList replacement, item recycling
    - **FlatList** (fallback): always provide \`getItemLayout\` for fixed-height items
    - All require \`estimatedItemSize\` or \`getItemLayout\`
    - Props to tune: \`windowSize\`, \`maxToRenderPerBatch\`, \`removeClippedSubviews\`, \`initialNumToRender\`
    - Always \`React.memo\` list items — see \`get-component-patterns\` (topic: react-memo)`,
    
      images: `## Images
    - ALWAYS use WebP format (2-3x smaller than PNG/JPG)
    - Use \`expo-image\` with \`cachePolicy="memory-disk"\`
    - See \`get-component-patterns\` (topic: expo-image) for full expo-image usage`,
    
      'tree-shaking': `## Tree-Shaking
    - Use direct submodule imports: \`import format from 'date-fns/format'\`
    - Never: \`import { format } from 'date-fns'\` (pulls entire package)`,
    
      'barrel-exports': `## Barrel Exports
    - Avoid \`index.ts\` re-export barrels — they prevent tree-shaking
    - Use direct file imports: \`import { Button } from '@/components/ui/Button'\`
    - Enforce with \`eslint-plugin-no-barrel-imports\``,
    
      bundle: `## Bundle Analysis
    - \`EXPO_UNSTABLE_ATLAS=true npx expo start --no-dev\` + \`npx expo-atlas\` (recommended)
    - \`npx expo export --dump-sourcemap\` + \`npx source-map-explorer output.js\``,
    
      concurrent: `## Concurrent React
    - \`useDeferredValue\`: defer rendering of a single value (search input → results)
    - \`useTransition\`: mark entire state updates as low priority (\`startTransition\`)
    - Wrap heavy components in \`React.memo\` when passing deferred values
    - Choose: \`useDeferredValue\` for one value, \`useTransition\` for state updates`,
    
      'interaction-manager': `## InteractionManager
    - \`InteractionManager.runAfterInteractions(() => heavyWork())\` — defer after animations
    - Always return \`() => task.cancel()\` in useEffect cleanup`,
    
      reanimated: `## Reanimated
    - Use \`react-native-reanimated\` worklets for 60+ FPS animations (runs on UI thread)
    - Core: \`useSharedValue\`, \`useAnimatedStyle\`, \`withSpring\` / \`withTiming\`
    - 16ms budget per frame (60 FPS) / 8ms for 120 FPS
    - JS thread stays free — animations never blocked by business logic`,
    
      checklist: `## Checklist
    - [ ] LegendList / FlashList for lists
    - [ ] estimatedItemSize / getItemLayout
    - [ ] React.memo for list items
    - [ ] expo-image + cachePolicy
    - [ ] WebP images
    - [ ] Tree-shaking imports
    - [ ] No barrel exports
    - [ ] React Compiler (React 19+)
    - [ ] Reanimated for animations
    - [ ] InteractionManager for post-animation work
    - [ ] useDeferredValue / useTransition`,
    };
    
    // ─── Export ──────────────────────────────────────────────────────────
    
    const pattern: PatternSections = {
      title: 'Performance Patterns',
      sections,
      compactSections,
    };
    
    export const getPerformancePatterns = (topic?: string, compact?: boolean): string =>
      resolvePattern(pattern, topic, compact);
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden. It describes what content is returned (patterns covering specific technologies) and mentions the 'compact' parameter affects output length, but doesn't disclose format, structure, permissions needed, rate limits, or error behavior. It adds some behavioral context but leaves gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured with two sentences: first states purpose and usage context, second explains parameter usage. It's appropriately sized and front-loaded with essential information, though the list of covered technologies is somewhat lengthy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 2-parameter tool with no annotations and no output schema, the description provides good purpose and usage context but lacks details about return format, structure, or what 'patterns' actually look like. It's adequate for basic understanding but incomplete for full agent comprehension.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both parameters thoroughly. The description mentions using 'topic' to get specific sections and implies 'compact' affects content, but adds minimal semantic value beyond what's in the schema descriptions. Baseline 3 is appropriate when schema does the work.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Get performance optimization patterns' with specific domains listed (lists, images, bundle size, animations). It distinguishes from some siblings like 'get-api-patterns' or 'get-typescript-patterns' by focusing on performance, but doesn't explicitly differentiate from 'get-memory-optimization' which might overlap.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit usage guidance: 'Call this when optimizing lists, images, bundle size, or animations.' It lists specific optimization contexts and mentions using the 'topic' parameter to get specific sections, giving clear when-to-use instructions without alternatives needed.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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