react-native-performance
React Native performance optimization techniques. Use when optimizing app performance.
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.
| name | react-native-performance |
|---|---|
| description | React 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