Back to Posts

Optimizing React Native Performance

By Lumina Software
react-nativeperformancemobile-developmentexpo

Optimizing React Native Performance

Performance is critical for mobile apps. Users expect smooth animations, fast load times, and responsive interactions. Here's how to optimize React Native apps for peak performance.

Performance Metrics That Matter

Key Indicators

  • Time to Interactive (TTI): When app becomes usable
  • Frame Rate: Target 60 FPS for smooth animations
  • Bundle Size: Smaller = faster downloads
  • Memory Usage: Prevent crashes and slowdowns
  • Startup Time: First impression matters

Rendering Optimization

1. Use FlashList Instead of FlatList

FlashList is significantly faster:

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

<FlashList
  data={items}
  renderItem={renderItem}
  estimatedItemSize={100} // Critical for performance
  onEndReached={loadMore}
  onEndReachedThreshold={0.5}
/>

Key differences:

  • Better recycling of views
  • More accurate size estimation
  • Improved scroll performance

2. Memoize Expensive Components

import { memo, useMemo } from 'react';

// Memoize component
const ExpensiveComponent = memo(({ data }) => {
  const processed = useMemo(
    () => expensiveComputation(data),
    [data]
  );
  
  return <View>{processed}</View>;
});

// Memoize callbacks
const handlePress = useCallback(() => {
  doSomething(id);
}, [id]);

3. Avoid Inline Functions and Objects

// ❌ Bad: Creates new function/object on every render
<Button onPress={() => handlePress(id)} style={{ margin: 10 }} />

// ✅ Good: Stable references
const handlePress = useCallback(() => handlePress(id), [id]);
const buttonStyle = useMemo(() => ({ margin: 10 }), []);

<Button onPress={handlePress} style={buttonStyle} />

4. Use React.memo Wisely

// Only memoize if props change infrequently
const UserCard = memo(({ user }) => {
  return <View>...</View>;
}, (prevProps, nextProps) => {
  // Custom comparison
  return prevProps.user.id === nextProps.user.id;
});

Image Optimization

Use expo-image

import { Image } from 'expo-image';

<Image
  source={{ uri: 'https://example.com/image.jpg' }}
  placeholder={blurhash} // Show placeholder while loading
  contentFit="cover"
  transition={200} // Smooth fade-in
  cachePolicy="memory-disk" // Cache strategy
/>

Image Sizing

// Always specify dimensions
<Image
  source={source}
  style={{ width: 200, height: 200 }} // Prevents layout shifts
  contentFit="cover"
/>

Lazy Loading

import { useInView } from 'react-native-intersection-observer';

function LazyImage({ source }) {
  const { ref, inView } = useInView();
  
  return (
    <View ref={ref}>
      {inView ? (
        <Image source={source} />
      ) : (
        <Placeholder />
      )}
    </View>
  );
}

Bundle Size Optimization

1. Code Splitting

// Lazy load heavy components
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <HeavyComponent />
    </Suspense>
  );
}

2. Tree Shaking

// ✅ Good: Import only what you need
import { debounce } from 'lodash-es';

// ❌ Bad: Imports entire library
import _ from 'lodash';

3. Analyze Bundle

# Analyze bundle size
npx react-native-bundle-visualizer

# Or with Expo
eas build --profile production --analyze

4. Remove Unused Dependencies

# Find unused dependencies
npx depcheck

# Remove unused packages
npm uninstall unused-package

Memory Management

1. Clean Up Subscriptions

useEffect(() => {
  const subscription = eventEmitter.subscribe(handleEvent);
  
  return () => {
    subscription.unsubscribe(); // Cleanup
  };
}, []);

2. Avoid Memory Leaks

// ❌ Bad: Storing references
const [data, setData] = useState([]);

useEffect(() => {
  fetchData().then(setData);
  // data array keeps growing, never cleared
}, []);

// ✅ Good: Clear old data
useEffect(() => {
  let cancelled = false;
  
  fetchData().then(newData => {
    if (!cancelled) {
      setData(newData);
    }
  });
  
  return () => {
    cancelled = true;
  };
}, []);

3. Use Weak References

// For caching that shouldn't prevent GC
const cache = new WeakMap();

function getCached(key: object) {
  if (!cache.has(key)) {
    cache.set(key, expensiveComputation(key));
  }
  return cache.get(key);
}

Network Optimization

1. Request Batching

// ❌ Bad: Multiple requests
const user = await fetchUser(id);
const posts = await fetchPosts(userId);
const comments = await fetchComments(postId);

// ✅ Good: Batch requests
const [user, posts, comments] = await Promise.all([
  fetchUser(id),
  fetchPosts(userId),
  fetchComments(postId),
]);

2. Request Deduplication

// Use TanStack Query for automatic deduplication
const { data } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
  staleTime: 5 * 60 * 1000, // Cache for 5 minutes
});

3. Optimistic Updates

const mutation = useMutation({
  mutationFn: updatePost,
  onMutate: async (newPost) => {
    // Cancel outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['posts'] });
    
    // Optimistically update
    queryClient.setQueryData(['posts'], (old) => [
      ...old,
      newPost,
    ]);
  },
});

Animation Performance

1. Use Native Driver

import { Animated } from 'react-native';

const animValue = useRef(new Animated.Value(0)).current;

Animated.timing(animValue, {
  toValue: 1,
  duration: 300,
  useNativeDriver: true, // ✅ Runs on UI thread
}).start();

2. Use Reanimated 3

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

const translateX = useSharedValue(0);

const handlePress = () => {
  translateX.value = withSpring(100); // Smooth spring animation
};

3. Avoid Layout Animations

// ❌ Bad: Triggers layout recalculation
Animated.timing(animValue, {
  toValue: 1,
  useNativeDriver: false, // Runs on JS thread
});

// ✅ Good: Transform only
Animated.timing(animValue, {
  toValue: 1,
  useNativeDriver: true, // Transform properties only
});

Startup Optimization

1. Lazy Load Initial Screen

// Load critical content first
function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Home"
          component={lazy(() => import('./screens/Home'))}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

2. Preload Critical Data

// Prefetch data before navigation
useEffect(() => {
  queryClient.prefetchQuery({
    queryKey: ['user'],
    queryFn: fetchUser,
  });
}, []);

3. Reduce Initial Bundle

// Split vendor bundles
// next.config.js or metro.config.js
module.exports = {
  // Split code into chunks
};

Profiling Tools

React Native Performance Monitor

import { PerformanceMonitor } from 'react-native-performance-monitor';

<PerformanceMonitor
  onMetrics={(metrics) => {
    console.log('FPS:', metrics.fps);
    console.log('Memory:', metrics.memory);
  }}
/>

Flipper Integration

// Use Flipper for debugging
// - Network inspector
// - Layout inspector
// - Performance profiler

Chrome DevTools

# Enable remote debugging
# Use Chrome DevTools for profiling

Best Practices Checklist

  • Use FlashList for long lists
  • Memoize expensive computations
  • Avoid inline functions/objects
  • Optimize images with expo-image
  • Code split heavy components
  • Clean up subscriptions
  • Use native driver for animations
  • Batch network requests
  • Monitor performance metrics
  • Test on real devices

Measuring Performance

Performance Monitoring

import { performance } from 'perf_hooks';

const start = performance.now();
await expensiveOperation();
const duration = performance.now() - start;

console.log(`Operation took ${duration}ms`);

Real User Monitoring

// Track real user metrics
analytics.track('app_startup_time', {
  duration: startupTime,
  device: deviceInfo,
});

Conclusion

Performance optimization is an ongoing process. Key principles:

  1. Measure first: Know your baseline
  2. Optimize bottlenecks: Focus on biggest wins
  3. Test on real devices: Simulators lie
  4. Monitor continuously: Track metrics over time
  5. Iterate: Performance is never "done"

With these techniques, your React Native app will feel fast, smooth, and responsive—exactly what users expect.