From react-native-toolkit
Optimizes React Native performance: profiles with Flipper/DevTools, reduces re-renders via memoization/useCallback, tunes FlatList, fixes bridge/memory issues.
How this skill is triggered — by the user, by Claude, or both
Slash command
/react-native-toolkit:performance-optimizerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Optimize React Native app performance across rendering, bridge communication, and memory usage.
Optimize React Native app performance across rendering, bridge communication, and memory usage.
Common performance issues and quick fixes:
Enable Performance Monitor:
// Shake device → "Show Perf Monitor"
// Or in code (dev only):
if (__DEV__) {
require('react-native').PerformanceLogger.startTimespan('AppStartup');
}
Use React DevTools Profiler:
npm install -g react-devtools
react-devtools
Use Flipper:
Common bottlenecks:
Use React.memo:
import React, { memo } from 'react';
const ExpensiveComponent = memo(({ data }) => {
return <View>{/* Expensive rendering */}</View>;
});
// With custom comparison
const ExpensiveComponent = memo(
({ data }) => <View>{data.value}</View>,
(prevProps, nextProps) => prevProps.data.id === nextProps.data.id
);
Use useMemo and useCallback:
import { useMemo, useCallback } from 'react';
function MyComponent({ items, onPress }) {
// Memoize expensive computations
const sortedItems = useMemo(
() => items.sort((a, b) => a.value - b.value),
[items]
);
// Memoize callbacks to prevent child re-renders
const handlePress = useCallback(
(id) => onPress(id),
[onPress]
);
return <List items={sortedItems} onPress={handlePress} />;
}
Avoid inline functions and objects:
// Bad - creates new function on every render
<Button onPress={() => handlePress(item.id)} />
// Good - use useCallback
const handleItemPress = useCallback(() => handlePress(item.id), [item.id]);
<Button onPress={handleItemPress} />
// Bad - creates new object on every render
<View style={{ marginTop: 10 }} />
// Good - define outside or use StyleSheet
const styles = StyleSheet.create({
container: { marginTop: 10 }
});
<View style={styles.container} />
Use FlatList with optimization props:
import { FlatList } from 'react-native';
<FlatList
data={items}
renderItem={({ item }) => <Item data={item} />}
keyExtractor={(item) => item.id}
// Performance optimizations
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
initialNumToRender={10}
windowSize={5}
// If item height is fixed
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
/>
Use FlashList for better performance:
npm install @shopify/flash-list
import { FlashList } from '@shopify/flash-list';
<FlashList
data={items}
renderItem={({ item }) => <Item data={item} />}
estimatedItemSize={100}
/>
Optimize list items:
// Memoize list items
const Item = memo(({ data }) => {
return (
<View>
<Text>{data.title}</Text>
</View>
);
});
// Use PureComponent for class components
class Item extends PureComponent {
render() {
return <View>{this.props.data.title}</View>;
}
}
Batch bridge calls:
// Bad - multiple bridge calls
items.forEach(item => {
NativeModule.processItem(item);
});
// Good - single bridge call
NativeModule.processItems(items);
Use InteractionManager:
import { InteractionManager } from 'react-native';
// Defer expensive operations until after animations
InteractionManager.runAfterInteractions(() => {
// Expensive operation
processData();
});
Move heavy operations to native:
// For CPU-intensive tasks, create native module
import { NativeModules } from 'react-native';
const { HeavyComputation } = NativeModules;
const result = await HeavyComputation.process(largeDataset);
Use FastImage:
npm install react-native-fast-image
import FastImage from 'react-native-fast-image';
<FastImage
source={{
uri: imageUrl,
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable,
}}
resizeMode={FastImage.resizeMode.cover}
style={{ width: 200, height: 200 }}
/>
Optimize image sizes:
// Use appropriate image sizes
<Image
source={{ uri: imageUrl }}
style={{ width: 100, height: 100 }}
resizeMode="cover"
/>
// Preload images
FastImage.preload([
{ uri: image1Url },
{ uri: image2Url },
]);
Use WebP format:
cwebp input.png -o output.webpUse native driver:
import { Animated } from 'react-native';
const fadeAnim = new Animated.Value(0);
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1000,
useNativeDriver: true, // Enable native driver
}).start();
Use Reanimated for complex animations:
npm install react-native-reanimated
import Animated, {
useAnimatedStyle,
withTiming,
useSharedValue,
} from 'react-native-reanimated';
function AnimatedComponent() {
const offset = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: withTiming(offset.value) }],
}));
return <Animated.View style={animatedStyle} />;
}
Clean up listeners:
useEffect(() => {
const subscription = eventEmitter.addListener('event', handler);
return () => {
subscription.remove(); // Cleanup
};
}, []);
Clean up timers:
useEffect(() => {
const timer = setTimeout(() => {
// Do something
}, 1000);
return () => {
clearTimeout(timer); // Cleanup
};
}, []);
Clean up async operations:
useEffect(() => {
let cancelled = false;
async function fetchData() {
const result = await api.fetch();
if (!cancelled) {
setData(result);
}
}
fetchData();
return () => {
cancelled = true; // Prevent state update after unmount
};
}, []);
import { VirtualizedList } from 'react-native';
<VirtualizedList
data={items}
renderItem={({ item }) => <Item data={item} />}
keyExtractor={(item) => item.id}
getItemCount={(data) => data.length}
getItem={(data, index) => data[index]}
/>
import React, { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
);
}
import { useCallback } from 'react';
import debounce from 'lodash/debounce';
function SearchComponent() {
const debouncedSearch = useCallback(
debounce((query) => {
performSearch(query);
}, 300),
[]
);
return (
<TextInput
onChangeText={debouncedSearch}
placeholder="Search..."
/>
);
}
function InfiniteList() {
const [page, setPage] = useState(1);
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const loadMore = async () => {
if (loading) return;
setLoading(true);
const newItems = await fetchItems(page);
setItems([...items, ...newItems]);
setPage(page + 1);
setLoading(false);
};
return (
<FlatList
data={items}
renderItem={({ item }) => <Item data={item} />}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loading ? <Loading /> : null}
/>
);
}
// Wrap components to profile
import { Profiler } from 'react';
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
console.log(`${id} took ${actualDuration}ms to render`);
}
<Profiler id="MyComponent" onRender={onRenderCallback}>
<MyComponent />
</Profiler>
import { PerformanceObserver, performance } from 'react-native-performance';
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(`${entry.name}: ${entry.duration}ms`);
});
});
observer.observe({ entryTypes: ['measure'] });
performance.mark('start');
// ... operation
performance.mark('end');
performance.measure('operation', 'start', 'end');
App feels sluggish:
High memory usage:
Slow startup:
Janky animations:
npx claudepluginhub p/armanzeroeight-react-native-toolkit-plugins-react-native-toolkitOptimizes React Native app performance via FlatList tweaks, React.memo/useMemo, FastImage caching, bundle size reduction, and profiling techniques.
Provides React Native performance optimization guidelines for FPS, TTI, bundle size, memory leaks, re-renders, and animations. Covers Hermes, FlashList, native modules, and profiling jank.
Diagnoses and optimizes React Native app performance: slow startup, janky scrolling, stuttering animations, large bundles, high memory.