Skip to main content

Optimizing Performance

1. Using Hermes Engine

Feature Description Availability
Hermes Engine JavaScript engine optimized for React Native and Expo apps NEW in Expo SDK 48
Performance Benefits Reduces app size, improves startup time, and enhances runtime performance Available in Expo Go and standalone apps
Enabling Hermes Enabled by default in Expo SDK 48+ for new projects Can be disabled via configuration

Example: Enabling Hermes in Expo Config

{
  "expo": {
    "jsEngine": "hermes"
  }
}
Note: Hermes is enabled by default in Expo SDK 48 and later versions. For older versions, you must explicitly configure it.

2. Enabling Performance Monitoring

Example: Setting up Expo Performance Monitoring

import { Performance } from 'expo'; // <span class="badge badge--new">NEW</span>

// Enable performance monitoring
Performance.setPerformanceMonitoringEnabled(true);

// Monitor specific operations
Performance.startMonitoring('navigation');
Performance.startMonitoring('api-call');
Method Description Notes
setPerformanceMonitoringEnabled(enabled) Enables or disables performance monitoring Available in Expo SDK 51+
startMonitoring(operation) Starts monitoring a specific operation Operation types: 'navigation', 'api-call', 'image-load'
stopMonitoring(operation) Stops monitoring a specific operation Must match the operation name used in startMonitoring
Note: Performance monitoring is disabled by default and must be explicitly enabled in production builds.
Warning: Enabling performance monitoring may impact app startup time and memory usage.

Performance Monitoring Configuration

  • Enabled by default in development builds
  • Must be explicitly enabled for production builds
  • Supports monitoring navigation, API calls, and image loading

3. Optimizing Images

Property Description Usage
source Image URI or local file path source={require('./assets/image.png')}
resizeMode How to scale image within bounds resizeMode="contain"
blurRadius Apply blur effect to image blurRadius={5}

Example: Using Image with Blur Effect

<Image
  source={require('./assets/background.jpg')}
  blurRadius={10}
  style={{ width: 300, height: 200 }}
/>
Note: For best performance, use resizeMode to prevent unnecessary scaling.

Image Optimization Tips

  • Use appropriate image sizes for display dimensions
  • Preload images to reduce loading delays
  • Use blurRadius for visual enhancements without increasing file size
Resize Mode Description Use Case
cover Crops image to fill container Backgrounds, thumbnails
contain Fits image within container, maintains aspect ratio Full-screen images
stretch Stretches image to fill container Fixed aspect ratio layouts
repeat Repeats image to fill container Patterns, tiles

4. Using FlatList

FlatList is a performant, optimized component for rendering large lists of data in React Native and Expo applications. It only renders the items that are currently visible on screen, significantly improving performance for large datasets.

Property Description Default
data Array of data to render Required
renderItem Function that renders a single item Required
keyExtractor Function to extract a unique key for each item Uses item.key or index
initialNumToRender Number of items to render initially 10
maxToRenderPerBatch Maximum number of items to render per batch 10
windowSize How far to render items ahead of the visible area 2
onEndReached Function to call when the end of the list is reached None
onEndReachedThreshold Distance from the end to trigger onEndReached 0.5

Example: Basic FlatList Usage

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

const renderItem = ({ item }) => (
  <View style={{ padding: 10 }}>
    <Text>{item.name}</Text>
  </View>
);

const MyList = () => {
  const data = [
    { id: '1', name: 'Item 1' },
    { id: '2', name: 'Item 2' },
    { id: '3', name: 'Item 3' },
  ];

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={item => item.id}
    />
  );
};

export default MyList;

Example: FlatList with onEndReached

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

const MyList = () => {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(false);

  const loadMore = () => {
    if (loading) return;
    setLoading(true);
    // Simulate API call
    setTimeout(() => {
      const newItems = Array.from({ length: 10 }, (_, i) => ({
        id: (items.length + i + 1).toString(),
        name: `Item ${items.length + i + 1}`,
      }));
      setItems(prev => [...prev, ...newItems]);
      setLoading(false);
    }, 1000);
  };

  const renderItem = ({ item }) => (
    <View style={{ padding: 10 }}>
      <Text>{item.name}</Text>
    </View>
  );

  return (
    <FlatList
      data={items}
      renderItem={renderItem}
      keyExtractor={item => item.id}
      onEndReached={loadMore}
      onEndReachedThreshold={0.5}
    />
  );
};

export default MyList;
Note: FlatList is more performant than ScrollView when rendering large lists because it only renders items that are visible on screen.
Warning: Always provide a keyExtractor to ensure proper item re-rendering and performance.

5. Implementing Memoization

Method Description Usage
useMemo Memorizes the result of a computation to avoid expensive recalculations. import { useMemo } from 'react';\n\nconst expensiveValue = useMemo(() => {\n return heavyComputation(data);\n}, [data]);
useCallback Memorizes a function to prevent re-creation on every render. import { useCallback } from 'react';\n\nconst handleClick = useCallback(() => {\n console.log('Button clicked');\n}, []);
Note: useMemo is ideal for expensive calculations, while useCallback is best for preventing function re-creation in child components.

Best Practices for Memoization

  • Use useMemo for expensive computations that depend on state or props.
  • Use useCallback for functions passed as props to child components.
  • Always provide dependencies array to useMemo and useCallback.

6. Avoiding Inline Functions

Function Type Description Impact Best Practice
Inline Function A function defined directly in the render method or as a prop Creates new function instances on every render, causing unnecessary re-renders Avoid defining functions inside render or as inline props
Arrow Function in Render Using arrow functions directly in JSX like <Button onPress={() => handlePress()}> Every render creates a new function, causing re-renders of components that depend on it Move to top-level or component scope

Example: Avoiding Inline Functions

// ❌ Bad: Inline function in render
function MyComponent() {
  return (
    <View>
      <Button title="Press" onPress={() => console.log('pressed')} />
    </View>
  );
}

// ✅ Good: Defined outside render
const handlePress = () => console.log('pressed');

function MyComponent() {
  return (
    <View>
      <Button title="Press" onPress={handlePress} />
    </View>
  );
}
Note: This is particularly important in FlatList or ScrollView where each item is a component that may re-render unnecessarily.

7. Optimizing Rerenders

Technique Description Usage
useMemo Memorizes expensive calculations to prevent unnecessary recomputations.
const expensiveValue = useMemo(() => {
  return heavyCalculation(data);
}, [data]);
useCallback Memorizes function definitions to prevent re-creation on every render.
const handleClick = useCallback(() => {
  console.log('Button clicked');
}, []);
React.memo Prevents re-rendering of components if props haven't changed.
const OptimizedComponent = React.memo(({ name }) => {
  return <Text>{name}</Text>;
});
Note: Use useMemo for expensive calculations and useCallback for functions to prevent unnecessary re-renders.

Best Practices

  • Use useMemo for expensive computations.
  • Wrap event handlers with useCallback.
  • Wrap components with React.memo to prevent unnecessary re-renders.

8. Using Native Driver

The Native Driver in Expo allows you to run animations and gestures with native performance, bypassing the JavaScript bridge for smoother interactions.

Feature Description Status
useNativeDriver Boolean flag to enable native driver for animations NEW
Animation API Supports Animated and react-native-reanimated NEW

Example: Using Native Driver with Reanimated

import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';\n\nconst AnimatedBox = () => {\n  const opacity = useSharedValue(1);\n\n  const animatedStyle = useAnimatedStyle(() => {\n    return {\n      opacity: opacity.value,\n    };\n  });\n\n  const handlePress = () => {\n    opacity.value = withTiming(opacity.value === 1 ? 0 : 1, { duration: 500 });\n  };\n\n  return (\n    <Animated.View style={[animatedStyle, { width: 100, height: 100, backgroundColor: 'red' }]}>\n      <TouchableOpacity onPress={handlePress}>\n        <Text>Tap to animate</Text>\n      </TouchableOpacity>\n    </Animated.View>\n  );\n};
Note: Native driver requires react-native-reanimated to be installed and configured properly.
Warning: Not all animation properties support native driver. Check the Reanimated documentation for supported properties.

9. Lazy Loading Components

Lazy loading components in Expo helps improve initial load times and reduce memory usage by deferring the loading of non-critical components until they are needed.

Approach Description Code Example
Dynamic Imports Use dynamic imports to load components asynchronously. const LazyComponent = React.lazy(() => import("./LazyComponent"));
Suspense Fallback Wrap lazy-loaded components with a Suspense component to show a fallback while loading. import React, { Suspense } from "react";\n\nfunction App() {\n return (\n <Suspense fallback="<div>Loading...</div>">\n <LazyComponent />\n </Suspense>\n );\n}
Note: Dynamic imports are supported in Expo, but ensure that the component is correctly structured to be imported asynchronously.

10. Reducing Bundle Size

Bundle size directly impacts app load times and performance. Expo apps can be optimized to reduce bundle size through various strategies.

Strategy Description Notes
Code Splitting Split code into smaller chunks that are loaded on demand. Use dynamic imports to load modules only when needed.
Asset Optimization Compress and optimize images and other assets. Use expo-asset to manage assets efficiently.
Dependency Management Remove unused dependencies and use lightweight alternatives. Use tools like bundlephobia to analyze bundle size.
Tree Shaking Eliminate unused code from the final bundle. Ensure your bundler supports tree shaking (e.g., Metro).

Example: Using Dynamic Imports

import &#x27;react&#x27;;
import &#x27;expo&#x27;;

const loadModule = async () => {
  const module = await import(&#x27;./heavyModule&#x27;);
  return module.default;
};

export default function App() {
  const [loadedModule, setLoadedModule] = useState(null);

  useEffect(() => {
    loadModule().then(setLoadedModule);
  }, []);

  return <View><Text>{loadedModule ? &#x27;Loaded&#x27; : &#x27;Loading...&#x27;}</Text></View>;
}

Example: Asset Optimization

import { Asset } from &#x27;expo-asset&#x27;;

const asset = Asset.fromModule(require(&#x27;./assets/image.jpg&#x27;));
const uri = asset.uri;

// Use the URI in an <Image> component
<Image source={{ uri }} style={{ width: 200, height: 200 }} />
Note: Use expo-image for better image handling and performance.

Key Tips for Reducing Bundle Size

  • Use expo-image for optimized image loading.
  • Implement code splitting with dynamic imports.
  • Avoid including large libraries or assets unnecessarily.
  • Use tools like bundlephobia to analyze and optimize dependencies.

11. Using Production Build

Build Type Description Command Notes
production Optimized build for deployment expo build:android Includes minification and tree-shaking
production Optimized build for deployment expo build:ios Includes minification and tree-shaking

Example: Building for Production

expo build:android --release-channel production

Example: Building for iOS Production

expo build:ios --release-channel production
Note: Production builds are optimized for performance and size.

12. Profiling with React Profiler

Example: Using React Profiler in Expo

import React, { Profiler } from 'react';
import { View, Text } from 'expo';

function MyComponent() {
  return (
    <View>
      <Text>Hello World</Text>
    </View>
  );
}

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime,
  interactions
) {
  console.log(&#x27;Component: &#x27; + id);
  console.log(&#x27;Phase: &#x27; + phase);
  console.log(&#x27;Actual Duration: &#x27; + actualDuration);
}

export default function App() {
  return (
    <Profiler id="MyComponent" onRender={onRenderCallback}>
      <MyComponent />
    </Profiler>
  );
}
Property Description Notes
id Unique identifier for the component being profiled Used to distinguish different components in the profiler output
onRender Callback function that receives profiling data Called after each render with performance metrics
Note: The React Profiler is available in Expo SDK 48 and later.
Warning: Profiling can impact performance and should be used in development only.

Was this article helpful?