Skip to main content

Performance Overview

Learn how to build high-performance React Native applications by understanding optimization techniques, performance bottlenecks, and best practices.

Performance Fundamentals

React Native apps run on three main threads:
  1. JavaScript Thread: Executes your JavaScript code and React logic
  2. Main/UI Thread: Handles UI rendering and user interactions
  3. Native Modules Thread: Processes native module calls
Performance issues typically occur when these threads are blocked or communication between them is inefficient.

JavaScript Engine

React Native supports multiple JavaScript engines: Hermes is optimized for React Native:
// Android - android/app/build.gradle
project.ext.react = [
    enableHermes: true
]
# iOS - Podfile
use_react_native!(
  :path => config[:reactNativePath],
  :hermes_enabled => true
)
Hermes Benefits:
  • Faster app startup time
  • Reduced memory usage
  • Smaller app size
  • Optimized bytecode compilation

JavaScriptCore (JSC)

Default engine on iOS, optional on Android:
enableHermes: false  // Use JSC instead

Performance Metrics

Key metrics to monitor:

Frame Rate

Target 60 FPS (16.67ms per frame) for smooth animations:
import { PerformanceObserver } from 'react-native';

const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    console.log(`${entry.name}: ${entry.duration}ms`);
  });
});

observer.observe({ entryTypes: ['measure'] });

JavaScript Thread Performance

import { InteractionManager } from 'react-native';

InteractionManager.runAfterInteractions(() => {
  // Expensive operation after animations complete
  processLargeDataset();
});

Memory Usage

Monitor memory allocation and garbage collection:
if (__DEV__) {
  const { performance } = require('react-native');
  console.log('Memory:', performance.memory);
}

Common Performance Issues

1. Unnecessary Re-renders

Use React.memo and useMemo to prevent re-renders:
import React, { memo, useMemo } from 'react';

const ExpensiveComponent = memo(({ data }) => {
  const processedData = useMemo(
    () => expensiveOperation(data),
    [data]
  );
  
  return <View>{processedData}</View>;
});

2. Bridge Overhead

Minimize JavaScript-to-native communication:
// Bad: Multiple bridge calls
for (let i = 0; i < 100; i++) {
  NativeModule.updateValue(i);
}

// Good: Batch operations
NativeModule.updateValues(Array.from({ length: 100 }, (_, i) => i));

3. Large Lists

Use FlatList or SectionList with proper optimizations:
import { FlatList } from 'react-native';

const OptimizedList = () => (
  <FlatList
    data={largeDataset}
    renderItem={renderItem}
    keyExtractor={item => item.id}
    getItemLayout={(data, index) => ({
      length: ITEM_HEIGHT,
      offset: ITEM_HEIGHT * index,
      index,
    })}
    initialNumToRender={10}
    maxToRenderPerBatch={10}
    windowSize={5}
    removeClippedSubviews={true}
  />
);
See Optimizing FlatList for details.

4. Heavy Computations

Move heavy computations off the main thread:
import { useEffect, useState } from 'react';

const HeavyComputation = ({ data }) => {
  const [result, setResult] = useState(null);
  
  useEffect(() => {
    // Use Web Worker or native module
    const worker = new Worker('./computation.worker.js');
    worker.postMessage(data);
    worker.onmessage = (e) => setResult(e.data);
    
    return () => worker.terminate();
  }, [data]);
  
  return <Text>{result}</Text>;
};

Optimization Techniques

Use Native Driver for Animations

import { Animated } from 'react-native';

const fadeAnim = new Animated.Value(0);

Animated.timing(fadeAnim, {
  toValue: 1,
  duration: 1000,
  useNativeDriver: true, // Run on native thread
}).start();

Optimize Images

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

// Use appropriate image formats and sizes
<Image
  source={{ uri: 'https://example.com/image.jpg' }}
  style={{ width: 200, height: 200 }}
  resizeMode="cover"
/>

// Consider FastImage for better performance
<FastImage
  source={{
    uri: 'https://example.com/image.jpg',
    priority: FastImage.priority.normal,
  }}
  style={{ width: 200, height: 200 }}
  resizeMode={FastImage.resizeMode.contain}
/>

Lazy Loading

import React, { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

const App = () => (
  <Suspense fallback={<LoadingSpinner />}>
    <HeavyComponent />
  </Suspense>
);

Code Splitting

Use inline requires for better startup time:
// Load module only when needed
const handlePress = () => {
  const HeavyModule = require('./HeavyModule').default;
  HeavyModule.doSomething();
};
See RAM Bundles and Inline Requires for details.

Development vs Production

Development Mode

Development builds include:
  • Additional warnings and checks
  • Source maps for debugging
  • Hot reloading overhead
  • Unoptimized code

Production Mode

Production builds are optimized:
# Android
cd android
./gradlew assembleRelease

# iOS
xcodebuild -workspace ios/App.xcworkspace \
  -scheme App \
  -configuration Release
Production optimizations:
  • Minified JavaScript
  • Removed PropTypes checks
  • Disabled dev warnings
  • Optimized native code
  • Bytecode precompilation (Hermes)

Performance Monitoring

React DevTools Profiler

import { Profiler } from 'react';

const onRenderCallback = (id, phase, actualDuration) => {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
};

<Profiler id="MyComponent" onRender={onRenderCallback}>
  <MyComponent />
</Profiler>

Performance Timeline API

import { performance } from 'react-native';

performance.mark('operation-start');
// ... expensive operation
performance.mark('operation-end');
performance.measure(
  'operation',
  'operation-start',
  'operation-end'
);

const measure = performance.getEntriesByName('operation')[0];
console.log(`Operation took ${measure.duration}ms`);

Native Performance Tools

Android:
  • Android Profiler in Android Studio
  • Systrace
  • Method tracing
iOS:
  • Instruments (Time Profiler, Allocations)
  • Xcode Debug Navigator
  • Metal System Trace

Platform-Specific Optimizations

Android

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

iOS

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO'
      config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 's'
    end
  end
end

Performance Checklist

  • Enable Hermes JavaScript engine
  • Use production builds for testing
  • Implement proper list virtualization
  • Optimize image loading and caching
  • Use native driver for animations
  • Minimize bridge traffic
  • Profile and eliminate bottlenecks
  • Implement code splitting
  • Monitor memory usage
  • Test on low-end devices

Next Steps

Build docs developers (and LLMs) love