react-native-performance

IvanTorresEdge's avatarfrom IvanTorresEdge

React Native performance optimization techniques. Use when optimizing app performance.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 1, 2026

When & Why to Use This Skill

This Claude skill provides a comprehensive toolkit for React Native performance optimization, offering expert guidance on profiling, render efficiency, and memory management. It helps developers identify bottlenecks and implement best practices like memoization, efficient list rendering with FlashList, and UI-thread animations to achieve a smooth 60fps mobile experience and reduced bundle sizes.

Use Cases

  • Diagnosing and fixing 'janky' UI transitions and stuttering animations by migrating logic to the UI thread using Reanimated.
  • Optimizing large data sets in lists by implementing FlashList and reducing unnecessary component re-renders with React.memo.
  • Reducing app bundle size and improving startup time by analyzing dependencies and optimizing library imports.
  • Managing memory usage and preventing leaks by implementing proper cleanup in useEffect hooks and optimizing image caching with expo-image.
namereact-native-performance
descriptionReact Native performance optimization techniques. Use when optimizing app performance.

React Native Performance Skill

This skill covers performance optimization for React Native apps.

When to Use

Use this skill when:

  • App feels slow or janky
  • Lists scroll poorly
  • Animations stutter
  • Bundle size is too large
  • Memory usage is high

Core Principle

MEASURE FIRST - Profile before optimizing. Fix actual bottlenecks, not perceived ones.

Profiling Tools

React DevTools Profiler

// Enable profiling in development
if (__DEV__) {
  // React DevTools will show component render times
}

Flipper

# Install Flipper for advanced debugging
# https://fbflipper.com/

Performance Monitor

// Show FPS and memory in development
import { LogBox } from 'react-native';

if (__DEV__) {
  // Performance overlay available via shake gesture
}

Render Optimization

Avoid Inline Functions

// ❌ Bad: Creates new function every render
<Button onPress={() => handlePress(item.id)} />

// ✅ Good: Stable function reference
const handlePress = useCallback((id: string) => {
  console.log(id);
}, []);

<Button onPress={() => handlePress(item.id)} />

// ✅ Better: If possible, pass id differently
const handleItemPress = useCallback(() => {
  console.log(item.id);
}, [item.id]);

<Button onPress={handleItemPress} />

Memoize Components

import { memo } from 'react';

interface ItemCardProps {
  item: Item;
  onPress: (id: string) => void;
}

export const ItemCard = memo(function ItemCard({
  item,
  onPress,
}: ItemCardProps): React.ReactElement {
  return (
    <TouchableOpacity onPress={() => onPress(item.id)}>
      <Text>{item.name}</Text>
    </TouchableOpacity>
  );
});

useMemo for Expensive Computations

import { useMemo } from 'react';

function FilteredList({ items, filter }: Props): React.ReactElement {
  // Only recompute when items or filter change
  const filteredItems = useMemo(() => {
    return items.filter((item) =>
      item.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);

  return <FlashList data={filteredItems} {...rest} />;
}

useCallback for Event Handlers

import { useCallback } from 'react';

function ItemList(): React.ReactElement {
  const handlePress = useCallback((id: string) => {
    // Handle press
  }, []);

  const renderItem = useCallback(({ item }: { item: Item }) => (
    <ItemCard item={item} onPress={handlePress} />
  ), [handlePress]);

  return (
    <FlashList
      data={items}
      renderItem={renderItem}
      estimatedItemSize={80}
    />
  );
}

List Performance

Use FlashList Instead of FlatList

import { FlashList } from '@shopify/flash-list';

// ✅ FlashList is 10x faster than FlatList
<FlashList
  data={items}
  renderItem={({ item }) => <ItemCard item={item} />}
  estimatedItemSize={80}  // Required for FlashList
  keyExtractor={(item) => item.id}
/>

Optimize Item Rendering

// ✅ Use getItemType for different item layouts
<FlashList
  data={items}
  renderItem={({ item }) => {
    if (item.type === 'header') return <Header />;
    return <Item item={item} />;
  }}
  getItemType={(item) => item.type}
  estimatedItemSize={60}
/>

// ✅ Use overrideItemLayout for variable heights
<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={60}
  overrideItemLayout={(layout, item) => {
    layout.size = item.type === 'header' ? 100 : 60;
  }}
/>

Image Optimization

Use expo-image

import { Image } from 'expo-image';

// ✅ expo-image is faster than React Native Image
<Image
  source={{ uri: imageUrl }}
  style={{ width: 200, height: 200 }}
  contentFit="cover"
  transition={200}
  cachePolicy="memory-disk"
/>

Optimize Image Size

// ✅ Request appropriately sized images
const imageUrl = `${baseUrl}?w=400&h=400`;

// ✅ Use blurhash for placeholders
<Image
  source={{ uri: imageUrl }}
  placeholder={blurhash}
  transition={200}
/>

Animation Optimization

Use Reanimated

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

// ✅ Animations run on UI thread
function AnimatedBox(): React.ReactElement {
  const scale = useSharedValue(1);

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));

  return <Animated.View style={animatedStyle} />;
}

Avoid JS-Driven Animations

// ❌ Bad: JS thread animations
const [scale, setScale] = useState(1);
Animated.timing(scale, { toValue: 2 }).start();

// ✅ Good: Reanimated (runs on UI thread)
const scale = useSharedValue(1);
scale.value = withSpring(2);

Bundle Size Optimization

Analyze Bundle

npx react-native-bundle-visualizer

Optimize Imports

// ❌ Bad: Imports entire library
import { format } from 'date-fns';

// ✅ Good: Import only what you need
import format from 'date-fns/format';

// ❌ Bad: Import all icons
import * as Icons from 'lucide-react-native';

// ✅ Good: Import specific icons
import { Home, Search } from 'lucide-react-native';

Replace Heavy Libraries

// Replace moment.js (300KB) with:
// - date-fns (tree-shakeable)
// - dayjs (2KB)

// Replace lodash with:
// - Individual lodash functions
// - Native JavaScript methods

Memory Management

Clean Up Effects

useEffect(() => {
  const subscription = eventEmitter.subscribe(handler);

  // ✅ Always clean up subscriptions
  return () => {
    subscription.unsubscribe();
  };
}, []);

Avoid Memory Leaks

// ✅ Cancel async operations
useEffect(() => {
  let isMounted = true;

  fetchData().then((data) => {
    if (isMounted) {
      setData(data);
    }
  });

  return () => {
    isMounted = false;
  };
}, []);

Common Pitfalls

Avoid

// ❌ Inline styles (creates new object every render)
<View style={{ padding: 10 }} />

// ❌ Anonymous functions in render
<Button onPress={() => handlePress()} />

// ❌ FlatList for long lists
<FlatList data={longList} />

// ❌ Unoptimized images
<Image source={{ uri: fullSizeImage }} />

Prefer

// ✅ NativeWind or StyleSheet
<View className="p-4" />

// ✅ useCallback for handlers
const handlePress = useCallback(() => {...}, []);

// ✅ FlashList for long lists
<FlashList data={longList} estimatedItemSize={80} />

// ✅ Optimized images with expo-image
<Image source={{ uri: optimizedImage }} cachePolicy="memory-disk" />

Performance Checklist

  • Use FlashList instead of FlatList
  • Memoize expensive computations
  • Use useCallback for event handlers
  • Use React.memo for list items
  • Use Reanimated for animations
  • Use expo-image for images
  • Analyze and optimize bundle size
  • Clean up effects and subscriptions
  • Profile before optimizing

Notes

  • Profile on actual devices, not simulators
  • Focus on 60fps for smooth experience
  • Monitor memory usage
  • Test on lower-end devices
  • Use New Architecture for better performance