Skip to main content
Back to Blog

React Native Performance Optimization: 5 Essential Tips

8 min read
React NativePerformanceOptimizationBest Practices

React Native Performance Optimization: 5 Essential Tips

Performance is crucial for mobile apps. Users expect smooth, responsive experiences, and even small delays can lead to frustration and app abandonment. In this post, I'll share five essential tips for optimizing your React Native applications.

1. Optimize FlatList Rendering

FlatList is one of the most commonly used components in React Native, but it can also be a performance bottleneck if not used correctly.

Key Optimizations:

import React from 'react';
import { FlatList, View, Text } from 'react-native';

interface Item {
  id: string;
  title: string;
}

const OptimizedList: React.FC<{ data: Item[] }> = ({ data }) => {
  const renderItem = React.useCallback(
    ({ item }: { item: Item }) => (
      <View>
        <Text>{item.title}</Text>
      </View>
    ),
    []
  );

  const keyExtractor = React.useCallback(
    (item: Item) => item.id,
    []
  );

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      // Performance props
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      initialNumToRender={10}
      windowSize={10}
    />
  );
};

Key Points:

  • Use React.useCallback for renderItem and keyExtractor
  • Set removeClippedSubviews={true} to unmount off-screen items
  • Adjust windowSize based on your item height
  • Use getItemLayout if items have fixed heights

2. Image Optimization

Images are often the largest assets in mobile apps. Proper optimization can significantly improve performance.

Best Practices:

import { Image } from 'react-native';
import FastImage from 'react-native-fast-image';

// Use FastImage for better caching and performance
const OptimizedImage = () => (
  <FastImage
    source={{
      uri: 'https://example.com/image.jpg',
      priority: FastImage.priority.normal,
    }}
    resizeMode={FastImage.resizeMode.cover}
    style={{ width: 200, height: 200 }}
  />
);

Tips:

  • Use react-native-fast-image for better caching
  • Resize images to the exact dimensions needed
  • Use WebP format for smaller file sizes
  • Implement lazy loading for images below the fold

3. Reduce Bundle Size

A smaller bundle means faster app startup and less memory usage.

Strategies:

  1. Enable Hermes Engine (if not already enabled):
// android/app/build.gradle
project.ext.react = [
    enableHermes: true
]
  1. Use Dynamic Imports:
// Instead of:
import HeavyComponent from './HeavyComponent';

// Use:
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
  1. Analyze Bundle Size:
npx react-native-bundle-visualizer

4. Memoization and useMemo

Prevent unnecessary re-renders with proper memoization.

import React, { useMemo } from 'react';

interface Props {
  items: Item[];
  filter: string;
}

const FilteredList: React.FC<Props> = ({ items, filter }) => {
  // Expensive computation memoized
  const filteredItems = useMemo(() => {
    return items.filter(item => 
      item.title.toLowerCase().includes(filter.toLowerCase())
    );
  }, [items, filter]);

  return (
    <FlatList
      data={filteredItems}
      renderItem={({ item }) => <ItemCard item={item} />}
    />
  );
};

// Memoize the component itself
export default React.memo(FilteredList);

5. Avoid Inline Functions and Objects

Inline functions and objects create new references on every render, causing unnecessary re-renders.

❌ Bad:

<TouchableOpacity
  onPress={() => handlePress(item.id)}
  style={{ padding: 10 }}
>
  <Text>Press me</Text>
</TouchableOpacity>

✅ Good:

const styles = StyleSheet.create({
  button: {
    padding: 10,
  },
});

const handlePress = useCallback(() => {
  handlePress(item.id);
}, [item.id]);

<TouchableOpacity
  onPress={handlePress}
  style={styles.button}
>
  <Text>Press me</Text>
</TouchableOpacity>

Bonus: Use the Performance Monitor

React Native includes a built-in performance monitor. Enable it in development:

// In your app's entry point
if (__DEV__) {
  require('react-native').LogBox.ignoreAllLogs();
  const { PerformanceMonitor } = require('react-native');
  PerformanceMonitor.show();
}

Measuring Performance

Always measure before and after optimization:

import { InteractionManager } from 'react-native';

const measurePerformance = () => {
  const start = Date.now();
  
  InteractionManager.runAfterInteractions(() => {
    const end = Date.now();
    console.log(`Operation took ${end - start}ms`);
  });
};

Conclusion

Performance optimization is an ongoing process. Start with these five tips, measure the impact, and iterate. Remember:

  1. Profile before optimizing
  2. Focus on the biggest bottlenecks first
  3. Test on real devices, not just simulators
  4. Monitor performance in production

What performance optimization techniques have worked best for you? Let me know in the comments!


Resources: