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 |
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
blurRadiusfor 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
useMemofor expensive computations that depend on state or props. - Use
useCallbackfor functions passed as props to child components. - Always provide dependencies array to
useMemoanduseCallback.
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. | |
| useCallback | Memorizes function definitions to prevent re-creation on every render. | |
| React.memo | Prevents re-rendering of components if props haven't changed. | |
Note: Use
useMemo for expensive calculations and useCallback for functions to prevent unnecessary re-renders.Best Practices
- Use
useMemofor expensive computations. - Wrap event handlers with
useCallback. - Wrap components with
React.memoto 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 'react';
import 'expo';
const loadModule = async () => {
const module = await import('./heavyModule');
return module.default;
};
export default function App() {
const [loadedModule, setLoadedModule] = useState(null);
useEffect(() => {
loadModule().then(setLoadedModule);
}, []);
return <View><Text>{loadedModule ? 'Loaded' : 'Loading...'}</Text></View>;
}
Example: Asset Optimization
import { Asset } from 'expo-asset';
const asset = Asset.fromModule(require('./assets/image.jpg'));
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-imagefor optimized image loading. - Implement code splitting with dynamic imports.
- Avoid including large libraries or assets unnecessarily.
- Use tools like
bundlephobiato 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 |
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('Component: ' + id);
console.log('Phase: ' + phase);
console.log('Actual Duration: ' + 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.