| name | react-native-web-performance |
| user-invocable | false |
| description | Use when optimizing React Native Web performance. Provides patterns for code splitting, bundle optimization, memoization, and web-specific performance improvements. |
| allowed-tools | ["Read","Write","Edit","Bash","Grep","Glob"] |
React Native Web - Performance
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';
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
});
function Component() {
return <View style={styles.container} />;
}
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=>{item.title}
{item.description}
);
});
() {
renderItem = (
(
),
[onItemPress]
);
keyExtractor = ( item., []);
(
);
}
styles = .({
: {
: ,
: ,
: ,
: ,
},
: {
: ,
: ,
: ,
},
: {
: ,
: ,
},
});
Dynamic Imports
import React, { lazy, Suspense, useState } from 'react';
import { View, Button, ActivityIndicator } from 'react-native';
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;
}) {
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) {
();
}
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 (
{isVisible ? children : }
);
}
Bundle Size Optimization
module.exports = {
resolve: {
alias: {
'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:
function Component() {
const styles = StyleSheet.create({ container: { flex: 1 } });
return <View style={styles.container} />;
}
const styles = StyleSheet.create({ container: { flex: 1 } });
function Component() {
return <View style={styles.container} />;
}
❌ Don't use inline functions in FlatList:
<FlatList
data={items}
renderItem={({ item }) => <Item item={item} onPress={() => handlePress(item.id)} />}
/>
const renderItem = useCallback(({ item }) => (
<Item item={item} onPress={handlePress} />
), [handlePress]);
<FlatList data={items} renderItem={renderItem} />
❌ Don't import entire libraries:
import _ from 'lodash';
import debounce from 'lodash/debounce';
❌ Don't render all items in long lists:
{items.map(item => <Item key={item.id} item={item} />)}
<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