Optimizing Performance
1. Using React.memo
React.memo is a higher-order component that memoizes the result of a functional component to prevent unnecessary re-renders when the props remain unchanged.
| Property | Description | Default |
|---|---|---|
| Component | The functional component to memoize. | - |
| compareFunction | Optional function to customize the comparison logic. | Shallow equality check |
Example: Basic Usage
import React, { memo } from 'react';
const ExpensiveComponent = memo(({ count }) => {
console.log("ExpensiveComponent rendered");
return <div>Count: {count}</div>;
});
export default ExpensiveComponent;
Example: Custom Comparison Function
import React, { memo } from 'react';
const CustomComponent = memo(({ data, title }) => {
return <div>{title}: {data}</div>;
}, (prevProps, nextProps) => {
return prevProps.data === nextProps.data;
});
Note:
React.memo only checks props for equality. It does not prevent re-renders due to state changes or context updates in parent components.
2. Implementing shouldComponentUpdate
| Method | Description | Return Value | Usage |
|---|---|---|---|
| shouldComponentUpdate | Controls whether a component should re-render | true to update, false to skip |
Override in class components to optimize rendering |
Example: shouldComponentUpdate implementation
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// Skip re-render if props or state haven't changed
return nextProps.data !== this.props.data ||
nextState.count !== this.state.count;
}
render() {
return <div>{this.props.data} - {this.state.count}</div>;
}
}
Note:
shouldComponentUpdate is not called for the initial render or when using forceUpdate().
Warning: Incorrectly implementing
shouldComponentUpdate can lead to inconsistent UI or missed updates.
3. Using FlatList Optimization Props
| Prop | Description | Values | Notes |
|---|---|---|---|
| data | Array of data to render | array |
Required |
| renderItem | Function that renders a single item | function |
Required |
| keyExtractor | Function to extract a unique key for each item | function |
Recommended for performance |
| initialNumToRender | Number of items to render initially | number |
Default: 10 |
| maxToRenderPerBatch | Maximum number of items to render per batch | number |
Default: 10 |
| windowSize | Number of items to render beyond the visible area | number |
Default: 21 |
| removeClippedSubviews | Removes clipped subviews from the view hierarchy | true or false |
Default: true |
| disableVirtualization | Disables virtualization for FlatList | true or false |
Default: false |
| getItemLayout | Function to calculate item layout | function |
Improves performance for fixed-height items |
Example: Basic FlatList Usage
import React from 'react';
import { FlatList } from 'react-native';
const renderItem = ({ item }) => (
<View style={{ padding: 10 }}>
<Text>{item.name}</Text>
</View>
);
const MyFlatList = () => {
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}
/>
);
};
Example: Optimized FlatList with windowSize
import React from 'react';
import { FlatList } from 'react-native';
const renderItem = ({ item }) => (
<View style={{ padding: 10 }}>
<Text>{item.name}</Text>
</View>
);
const OptimizedFlatList = () => {
const data = Array.from({ length: 1000 }, (_, i) => ({
id: i.toString(),
name: `Item ${i}`,
}));
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
windowSize={5}
maxToRenderPerBatch={10}
initialNumToRender={10}
/>
);
};
4. Avoiding Inline Functions and Objects
| Pattern | Description | Impact | Better Alternative |
|---|---|---|---|
| inline function in render | listItems = data.map((item) => <ListItem key={item.id} onPress={() => handlePress(item)} />); |
Creates new function instance on every render | const handlePress = (item) => { /* logic */ }; listItems = data.map((item) => <ListItem key={item.id} onPress={() => handlePress(item)} />); |
| inline object in render | <View style={{ backgroundColor: 'red', padding: 10 }}> |
Creates new object instance on every render | const viewStyle = { backgroundColor: 'red', padding: 10 }; <View style={viewStyle}> |
| inline array in render | <FlatList data={data} renderItem={({ item }) => <Text style={[{ color: 'blue' }, item.style]}>{item.text}</Text>} /> |
Creates new array instance on every render | const textStyle = [{ color: 'blue' }]; <FlatList data={data} renderItem={({ item }) => <Text style={[textStyle, item.style]}>{item.text}</Text>} /> |
Example: Avoiding Inline Functions
const MyComponent = ({ items, onPress }) => {
// ❌ Bad: Inline function creates new instance each render
const renderItem = ({ item }) => (
<TouchableOpacity onPress={() => onPress(item)}>
<Text>{item.title}</Text>
</TouchableOpacity>
);
return <FlatList data={items} renderItem={renderItem} />;
};
// ✅ Good: Move function outside component
const MyComponent = ({ items, onPress }) => {
const handlePress = (item) => onPress(item);
const renderItem = ({ item }) => (
<TouchableOpacity onPress={() => handlePress(item)}>
<Text>{item.title}</Text>
</TouchableOpacity>
);
return <FlatList data={items} renderItem={renderItem} />;
};
Example: Avoiding Inline Objects
const MyComponent = ({ title }) => {
// ❌ Bad: Inline object creates new instance each render
return <View style={{ backgroundColor: 'red', padding: 10 }}>
<Text style={{ fontSize: 16, color: 'white' }}>{title}</Text>
</View>;
};
// ✅ Good: Define styles outside component
const containerStyle = {
backgroundColor: 'red',
padding: 10,
};
const textStyle = {
fontSize: 16,
color: 'white',
};
const MyComponent = ({ title }) => (
<View style={containerStyle}>
<Text style={textStyle}>{title}</Text>
</View>
);
5. Using useCallback and useMemo
Example: Using useCallback for event handlers
import React, { useCallback } from 'react';
const Button = ({ onClick }) => {
return <button onClick={onClick}>Click me</button>;
};
const ParentComponent = () => {
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return <Button onClick={handleClick} />;
};
| Hook | Description | Use Case |
|---|---|---|
| useCallback | Memorizes a function to prevent unnecessary re-renders | Passing functions as props to child components |
| useMemo | Memorizes a value to prevent expensive recalculations | Expensive computations or object creation |
Example: Using useMemo for expensive calculations
import React, { useMemo } from 'react';
const ExpensiveComponent = ({ data }) => {
const expensiveValue = useMemo(() => {
// Simulate expensive calculation
return data.reduce((sum, item) => sum + item.value, 0);
}, [data]);
return <div>Sum: {expensiveValue}</div>;
};
Note: Both hooks should only be used when necessary to avoid performance overhead.
6. Implementing Image Optimization
Example: Using React Native's Image Component with Resize Mode
import React from 'react';
import { Image, StyleSheet } from 'react-native';
const OptimizedImage = () => {
return (
<Image
source={{ uri: 'https://example.com/image.jpg' }}
style={styles.image}
resizeMode="cover"
/>
);
};
const styles = StyleSheet.create({
image: {
width: 300,
height: 200,
},
});
export default OptimizedImage;
| Property | Description | Values |
|---|---|---|
| resizeMode | How to resize the image when it doesn't fit its container | cover, contain, stretch, repeat, center |
| source | Image source (URI or local resource) | { uri: "url" }, { require("./image.png") } |
| blurRadius | Applies a blur effect to the image | number |
Note: For best performance, always provide images in appropriate sizes and formats (e.g., WebP or AVIF).
Example: Preloading Images
import React, { useEffect } from 'react';
import { Image } from 'react-native';
const preloadImage = (uri) => {
Image.prefetch(uri);
};
const MyComponent = () => {
useEffect(() => {
preloadImage('https://example.com/image.jpg');
}, []);
return <Image source={{ uri: 'https://example.com/image.jpg' }} />;
};
Image Optimization Best Practices
- Use appropriate image sizes to avoid unnecessary loading
- Preload images that are likely to be displayed soon
- Use
resizeModeto control how images are scaled - Consider using
blurRadiusfor a smoother loading experience
7. Reducing Bundle Size
Example: Using React Native's Built-in Code Splitting
import { lazy, Suspense } from 'react';
import { ActivityIndicator, View } from 'react-native';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback=<View style={{ flex: 1, justifyContent: 'center' }}><ActivityIndicator /></View>>
<HeavyComponent />
</Suspense>
);
}
| Technique | Description | Impact | Notes |
|---|---|---|---|
| Code Splitting | Splitting code into smaller chunks to load only what's needed. | Reduces initial bundle size | Use lazy and Suspense for dynamic imports |
| Tree Shaking | Removing unused code from the bundle during build. | Reduces bundle size | Works best with ES6 modules and proper build tools |
| Image Optimization | Using appropriate image formats and sizes. | Reduces asset size | Use require for static assets and resizeMode for dynamic images |
| Minification | Compressing JavaScript and CSS files. | Reduces file size | Enabled by default in production builds |
Note: Bundle size can be analyzed using tools like
react-native-bundle-visualizer.
Warning: Avoid importing large libraries directly; instead, use specific imports or lazy loading.
8. Using Hermes Engine
Note: Hermes is a JavaScript engine optimized for React Native apps, designed to improve app startup time, reduce memory usage, and enhance overall performance.
| Feature | Description | Status |
|---|---|---|
| Hermes Engine | JavaScript engine optimized for React Native apps | NEW |
| Startup Time | Reduces app startup time by pre-compiling JavaScript to bytecode | NEW |
| Memory Usage | Reduces memory consumption compared to JavaScriptCore | NEW |
| Bytecode Compilation | Compiles JavaScript to bytecode at build time | NEW |
Example: Enabling Hermes in React Native
// In android/app/build.gradle
project.ext.react = [
entryFile: "index.js",
enableHermes: true // Enable Hermes
]
// In ios/Podfile
use_react_native!(
:hermes_enabled => true
)
Benefits of Hermes
- Improved app startup time
- Reduced memory footprint
- Better JavaScript execution performance
- Enhanced app responsiveness
Hermes vs JavaScriptCore
- Hermes: Optimized for mobile, faster startup, lower memory usage
- JavaScriptCore: Default engine, more mature but less optimized for mobile
Enabling Hermes in React Native
- Update
android/app/build.gradleto enable Hermes - Update
ios/Podfileto use Hermes - Rebuild the app for Android and iOS
- Verify performance improvements using profiling tools
9. Implementing Code Splitting
Example: Using React.lazy and Suspense
import React, { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback="<div>Loading...</div>">
<HeavyComponent />
</Suspense>
);
}
| Feature | Description | Notes |
|---|---|---|
| React.lazy | Lazy loads components | Must be used with Suspense |
| Suspense | Handles loading states | Wraps lazy-loaded components |
Note: Code splitting with
React.lazy is supported in React 16.6+.10. Profiling with React DevTools
Example: Enable Profiling in React DevTools
// In your app entry point
import { AppRegistry } from 'react-native';
import { App } from './App';
// Wrap your app with the profiling provider
const ProfilingApp = () => {
return <App />;
};
AppRegistry.registerComponent('MyApp', () => ProfilingApp);
| Feature | Description | Availability |
|---|---|---|
| React DevTools Profiler | Measures component render times and identifies performance bottlenecks | NEW React Native 0.72+ |
| Render Duration | Time spent rendering a component in milliseconds | Available in all versions |
| Re-renders | Counts how many times a component re-renders | Available in all versions |
Note: Profiling is only available in development builds. It has minimal impact on production performance.
Steps to Profile Performance
- Enable Developer Menu in your app (shake device or press
Ctrl+M) - Navigate to Debugging and select Start Profiling
- Perform actions that you want to analyze (e.g., scrolling, tapping)
- Stop profiling and review the results in the React DevTools
Profiling vs. Other Tools
- React DevTools Profiler: Built-in, easy to use, good for component-level analysis
- Flipper: More advanced, supports network and storage inspection
- Chrome DevTools: For web-based React Native apps, limited support for native components
11. Optimizing Navigation Performance
| Property | Description | Notes |
|---|---|---|
| initialRouteName | Specifies the route to navigate to on first render | Improves startup performance by avoiding unnecessary navigation |
| screenOptions | Global options for all screens in navigator | Can reduce re-renders by avoiding inline object creation |
| lazy | Enables lazy loading of screens | Improves initial load time by deferring screen rendering |
Example: Using lazy loading
import { createStackNavigator } from '@react-navigation/stack';
const Stack = createStackNavigator();
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} lazy={true} />
</Stack.Navigator>
Note: Lazy loading is particularly effective for screens with heavy assets or complex UIs.
Navigation Performance Comparison
- Standard Navigation: All screens rendered immediately on app launch
- Lazy Loading: Screens rendered only when accessed
12. Reducing Re-renders
| Technique | Description | Usage |
|---|---|---|
| React.memo | Prevents unnecessary re-renders of functional components | <const MemoizedComponent = React.memo(MyComponent);> |
| useMemo | Memorizes expensive calculations to avoid recomputation | <const expensiveValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);> |
| useCallback | Memorizes function definitions to prevent re-creation on every render | <const handleClick = useCallback(() => { console.log('clicked'); }, []);> |
| shouldComponentUpdate (class components) | Control re-rendering in class components | <shouldComponentUpdate(nextProps, nextState) { return nextProps.id !== this.props.id; }> |
Note: Use
React.memo with useCallback for props to prevent unnecessary re-renders.