ð react-native-web-performance
Use when optimizing React Native Web performance. Provides patterns for code splitting, bundle optimization, memoization, and web-specific performance improvements.
Overview
Performance optimization patterns for React Native Web, focusing on bundle size, rendering performance, and web-specific optimizations.
Key Concepts
Code Splitting
Use dynamic imports for lazy loading:
import React, { lazy, Suspense } from 'react';
import { View, ActivityIndicator } from 'react-native';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<ActivityIndicator />}>
<HeavyComponent />
</Suspense>
);
}
Memoization
Prevent unnecessary re-renders with React.memo and hooks:
import React, { memo, useMemo, useCallback } from 'react';
interface Props {
items: Item[];
onItemPress: (id: string) => void;
}
export const ItemList = memo(function ItemList({ items, onItemPress }: Props) {
const sortedItems = useMemo(
() => items.sort((a, b) => a.name.localeCompare(b.name)),
[items]
);
const handlePress = useCallback(
(id: string) => {
onItemPress(id);
},
[onItemPress]
);
return (
<View>
{sortedItems.map(item => (
<Item key={item.id} item={item} onPress={handlePress} />
))}
</View>
);
});
FlatList for Large Lists
Use FlatList for efficient rendering of long lists:
import { FlatList, View, Text } from 'react-native';
interface Item {
id: string;
title: string;
}
function ItemsList({ items }: { items: Item[] }) {
return (
<FlatList
data={items}
keyExtractor={item => item.id}
renderItem={({ item }) => (
<View>
<Text>{item.title}</Text>
</View>
)}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
removeClippedSubviews
/>
);
}
Best Practices
Optimize Images
â Use optimized image formats and lazy loading:
import { Image } from 'react-native';
function OptimizedImage({ uri }: { uri: string }) {
return (
<Image
source={{ uri }}
style={{ width: 200, height: 200 }}
resizeMode="cover"
// Web-specific: lazy loading
{...(Platform.OS === 'web' && {
loading: 'lazy',
})}
/>
);
}
Avoid Inline Functions
â Use useCallback for event handlers:
import { useCallback } from 'react';
function Component({ onSave }: { onSave: (data: Data) => void }) {
const [data, setData] = useState<Data>();
const handleSave = useCallback(() => {
if (data) {
onSave(data);
}
}, [data, onSave]);
return <Button onPress={handleSave} title="Save" />;
}
Optimize StyleSheet
â Create StyleSheet outside component:
import { StyleSheet } from 'react-native';
// Good - created once
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
});
function Component() {
return <View style={styles.container} />;
}
// Bad - recreated on every render
function BadComponent() {
const styles = StyleSheet.create({
container: { flex: 1 },
});
return <View style={styles.container} />;
}
Examples
Virtualized List with Optimization
import React, { useCallback, memo } from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
interface Item {
id: string;
title: string;
description: string;
}
interface ItemCardProps {
item: Item;
onPress: (id: string) => void;
}
const ItemCard = memo(function ItemCard({ item, onPress }: ItemCardProps) {
const handlePress = useCallback(() => {
onPress(item.id);
}, [item.id, onPress]);
return (
<Pressable onPress={handlePress}>
<View style={styles.card}>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.description}>{item.description}</Text>
</View>
</Pressable>
);
});
export function OptimizedList({ items, onItemPress }: {
items: Item[];
onItemPress: (id: string) => void;
}) {
const renderItem = useCallback(
({ item }: { item: Item }) => (
<ItemCard item={item} onPress={onItemPress} />
),
[onItemPress]
);
const keyExtractor = useCallback((item: Item) => item.id, []);
return (
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={keyExtractor}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
removeClippedSubviews
getItemLayout={(data, index) => ({
length: 80,
offset: 80 * index,
index,
})}
/>
);
}
const styles = StyleSheet.create({
card: {
padding: 16,
backgroundColor: '#fff',
marginBottom: 8,
borderRadius: 8,
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 4,
},
description: {
fontSize: 14,
color: '#666',
},
});
Dynamic Imports
import React, { lazy, Suspense, useState } from 'react';
import { View, Button, ActivityIndicator } from 'react-native';
// Lazy load heavy components
const Chart = lazy(() => import('./Chart'));
const DataTable = lazy(() => import('./DataTable'));
export function Dashboard() {
const [showChart, setShowChart] = useState(false);
return (
<View>
<Button
title="Show Chart"
onPress={() => setShowChart(true)}
/>
{showChart && (
<Suspense fallback={<ActivityIndicator />}>
<Chart />
</Suspense>
)}
</View>
);
}
Optimized Context
import React, { createContext, useContext, useMemo, ReactNode } from 'react';
interface User {
id: string;
name: string;
}
interface UserContextValue {
user: User | null;
isLoading: boolean;
}
const UserContext = createContext<UserContextValue | undefined>(undefined);
export function UserProvider({
user,
isLoading,
children,
}: {
user: User | null;
isLoading: boolean;
children: ReactNode;
}) {
// Memoize context value to prevent unnecessary re-renders
const value = useMemo(
() => ({ user, isLoading }),
[user, isLoading]
);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
export function useUser() {
const context = useContext(UserContext);
if (context === undefined) {
throw new Error('useUser must be used within UserProvider');
}
return context;
}
Common Patterns
Debounced Input
import { useState, useEffect } from 'react';
import { TextInput } from 'react-native';
function SearchInput({ onSearch }: { onSearch: (query: string) => void }) {
const [query, setQuery] = useState('');
useEffect(() => {
const timer = setTimeout(() => {
onSearch(query);
}, 300);
return () => clearTimeout(timer);
}, [query, onSearch]);
return (
<TextInput
value={query}
onChangeText={setQuery}
placeholder="Search..."
/>
);
}
Intersection Observer (Web)
import { useEffect, useRef, useState } from 'react';
import { View, Platform } from 'react-native';
function LazyLoadComponent({ children }: { children: React.ReactNode }) {
const [isVisible, setIsVisible] = useState(false);
const ref = useRef<View>(null);
useEffect(() => {
if (Platform.OS !== 'web') {
setIsVisible(true);
return;
}
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
const element = ref.current as any;
if (element) {
observer.observe(element);
}
return () => observer.disconnect();
}, []);
return (
<View ref={ref}>
{isVisible ? children : <View style={{ height: 200 }} />}
</View>
);
}
Bundle Size Optimization
// webpack.config.js or metro.config.js
module.exports = {
resolve: {
alias: {
// Use lightweight alternatives
'react-native$': 'react-native-web',
'lodash': 'lodash-es',
},
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
},
},
},
},
};
Anti-Patterns
â Don't create StyleSheet inside render:
// Bad
function Component() {
const styles = StyleSheet.create({ container: { flex: 1 } });
return <View style={styles.container} />;
}
// Good
const styles = StyleSheet.create({ container: { flex: 1 } });
function Component() {
return <View style={styles.container} />;
}
â Don't use inline functions in FlatList:
// Bad
<FlatList
data={items}
renderItem={({ item }) => <Item item={item} onPress={() => handlePress(item.id)} />}
/>
// Good
const renderItem = useCallback(({ item }) => (
<Item item={item} onPress={handlePress} />
), [handlePress]);
<FlatList data={items} renderItem={renderItem} />
â Don't import entire libraries:
// Bad
import _ from 'lodash';
// Good
import debounce from 'lodash/debounce';
â Don't render all items in long lists:
// Bad
{items.map(item => <Item key={item.id} item={item} />)}
// Good
<FlatList
data={items}
renderItem={({ item }) => <Item item={item} />}
/>
Related Skills
- react-native-web-core: Core React Native Web concepts
- react-native-web-styling: Optimized styling patterns
- react-native-web-testing: Performance testing strategies