
React Native continues to dominate the cross-platform development space, allowing developers to build powerful mobile apps with a single codebase. But building a great app isn’t just about writing code – it’s about using the right tools. In 2025, several libraries have emerged as must-haves for any serious React Native project.
Here’s a comprehensive guide to the essential React Native libraries for 2025, complete with installation instructions and practical code examples.
1. React Navigation
Managing navigation in mobile apps can be tricky, but React Navigation makes it clean and efficient. Whether you need simple stack navigation or complex nested tabs, this library is the go-to solution.
# Installation
npm install @react-navigation/native @react-navigation/stack @react-navigation/bottom-tabs
npm install react-native-screens react-native-safe-area-context
// App.tsx - Complete Navigation Setup
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
// Type-safe navigation
export type RootStackParamList = {
Main: undefined;
ProductDetail: { productId: string };
Settings: undefined;
};
export type MainTabParamList = {
Home: undefined;
Search: { query?: string };
Cart: undefined;
Profile: undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<MainTabParamList>();
function MainTabs() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
const iconName = {
Home: focused ? 'home' : 'home-outline',
Search: focused ? 'search' : 'search-outline',
Cart: focused ? 'cart' : 'cart-outline',
Profile: focused ? 'person' : 'person-outline',
}[route.name] as keyof typeof Ionicons.glyphMap;
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: 'gray',
})}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Search" component={SearchScreen} />
<Tab.Screen
name="Cart"
component={CartScreen}
options={{
tabBarBadge: 3, // Show item count
}}
/>
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
}
export default function App() {
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Main: {
screens: {
Home: 'home',
Search: 'search/:query?',
Cart: 'cart',
Profile: 'profile',
},
},
ProductDetail: 'product/:productId',
Settings: 'settings',
},
},
};
return (
<NavigationContainer linking={linking}>
<Stack.Navigator>
<Stack.Screen
name="Main"
component={MainTabs}
options={{ headerShown: false }}
/>
<Stack.Screen
name="ProductDetail"
component={ProductDetailScreen}
options={({ route }) => ({
title: 'Product Details',
animation: 'slide_from_right',
})}
/>
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
// Type-safe navigation hook
import { useNavigation, useRoute } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { RouteProp } from '@react-navigation/native';
type ProductDetailRouteProp = RouteProp<RootStackParamList, 'ProductDetail'>;
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
function ProductCard({ productId }: { productId: string }) {
const navigation = useNavigation<NavigationProp>();
return (
<TouchableOpacity
onPress={() => navigation.navigate('ProductDetail', { productId })}
>
{/* Card content */}
</TouchableOpacity>
);
}
function ProductDetailScreen() {
const route = useRoute<ProductDetailRouteProp>();
const { productId } = route.params;
// Use productId to fetch data
}
2. Reanimated 3
Animations bring apps to life. Reanimated 3 has revolutionized how we think about performance and fluidity in animations. Built on the new architecture, it delivers native-driven 60FPS animations.
# Installation
npm install react-native-reanimated
// Animated Card Component
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
interpolate,
Extrapolate,
} from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
function AnimatedCard({ children }: { children: React.ReactNode }) {
const scale = useSharedValue(1);
const pressed = useSharedValue(false);
const tapGesture = Gesture.Tap()
.onBegin(() => {
pressed.value = true;
scale.value = withSpring(0.95);
})
.onFinalize(() => {
pressed.value = false;
scale.value = withSpring(1);
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
opacity: interpolate(scale.value, [0.95, 1], [0.8, 1]),
}));
return (
<GestureDetector gesture={tapGesture}>
<Animated.View style={[styles.card, animatedStyle]}>
{children}
</Animated.View>
</GestureDetector>
);
}
// Swipeable List Item
function SwipeableItem({ onDelete }: { onDelete: () => void }) {
const translateX = useSharedValue(0);
const itemHeight = useSharedValue(70);
const marginVertical = useSharedValue(10);
const opacity = useSharedValue(1);
const panGesture = Gesture.Pan()
.activeOffsetX([-10, 10])
.onUpdate((event) => {
translateX.value = Math.max(-100, Math.min(0, event.translationX));
})
.onEnd(() => {
if (translateX.value < -80) {
// Delete animation
translateX.value = withTiming(-500);
itemHeight.value = withTiming(0);
marginVertical.value = withTiming(0);
opacity.value = withTiming(0, {}, () => {
runOnJS(onDelete)();
});
} else {
translateX.value = withSpring(0);
}
});
const itemStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
height: itemHeight.value,
marginVertical: marginVertical.value,
opacity: opacity.value,
}));
const deleteButtonStyle = useAnimatedStyle(() => ({
opacity: interpolate(
translateX.value,
[-100, 0],
[1, 0],
Extrapolate.CLAMP
),
}));
return (
<View style={styles.container}>
<Animated.View style={[styles.deleteButton, deleteButtonStyle]}>
<Text style={styles.deleteText}>Delete</Text>
</Animated.View>
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.item, itemStyle]}>
{/* Item content */}
</Animated.View>
</GestureDetector>
</View>
);
}
// Animated Progress Bar
function AnimatedProgressBar({ progress }: { progress: number }) {
const width = useSharedValue(0);
useEffect(() => {
width.value = withTiming(progress, { duration: 500 });
}, [progress]);
const progressStyle = useAnimatedStyle(() => ({
width: `${width.value}%`,
backgroundColor: interpolateColor(
width.value,
[0, 50, 100],
['#ff4444', '#ffaa00', '#44ff44']
),
}));
return (
<View style={styles.progressContainer}>
<Animated.View style={[styles.progressBar, progressStyle]} />
</View>
);
}
3. TanStack Query (React Query)
Managing server state efficiently is crucial for modern applications. TanStack Query simplifies data fetching, caching, and synchronization.
# Installation
npm install @tanstack/react-query
// Setup and Usage
import { QueryClient, QueryClientProvider, useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
retry: 2,
refetchOnWindowFocus: false,
},
},
});
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Navigation />
</QueryClientProvider>
);
}
// API functions
const api = {
getProducts: async (category?: string): Promise<Product[]> => {
const response = await fetch(
`https://api.example.com/products${category ? `?category=${category}` : ''}`
);
if (!response.ok) throw new Error('Failed to fetch products');
return response.json();
},
getProduct: async (id: string): Promise<Product> => {
const response = await fetch(`https://api.example.com/products/${id}`);
if (!response.ok) throw new Error('Failed to fetch product');
return response.json();
},
createProduct: async (data: CreateProductInput): Promise<Product> => {
const response = await fetch('https://api.example.com/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) throw new Error('Failed to create product');
return response.json();
},
};
// Custom hooks
function useProducts(category?: string) {
return useQuery({
queryKey: ['products', category],
queryFn: () => api.getProducts(category),
});
}
function useProduct(id: string) {
return useQuery({
queryKey: ['product', id],
queryFn: () => api.getProduct(id),
enabled: !!id,
});
}
function useCreateProduct() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: api.createProduct,
onSuccess: (newProduct) => {
// Invalidate and refetch products list
queryClient.invalidateQueries({ queryKey: ['products'] });
// Optionally add to cache directly
queryClient.setQueryData(['product', newProduct.id], newProduct);
},
});
}
// Usage in components
function ProductList({ category }: { category?: string }) {
const { data: products, isLoading, error, refetch } = useProducts(category);
if (isLoading) return <ActivityIndicator />;
if (error) return (
<View>
<Text>Error: {error.message}</Text>
<Button title="Retry" onPress={() => refetch()} />
</View>
);
return (
<FlatList
data={products}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <ProductCard product={item} />}
refreshing={isLoading}
onRefresh={refetch}
/>
);
}
// Optimistic updates
function useToggleFavorite() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (productId: string) => {
const response = await fetch(`/api/products/${productId}/favorite`, {
method: 'POST',
});
return response.json();
},
onMutate: async (productId) => {
await queryClient.cancelQueries({ queryKey: ['product', productId] });
const previous = queryClient.getQueryData(['product', productId]);
queryClient.setQueryData(['product', productId], (old: Product) => ({
...old,
isFavorite: !old.isFavorite,
}));
return { previous };
},
onError: (err, productId, context) => {
queryClient.setQueryData(['product', productId], context?.previous);
},
onSettled: (data, error, productId) => {
queryClient.invalidateQueries({ queryKey: ['product', productId] });
},
});
}
4. Zustand – Lightweight State Management
For simpler state management needs, Zustand offers a minimal, fast, and scalable solution without the boilerplate of Redux.
# Installation
npm install zustand
// stores/useStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface CartItem {
productId: string;
name: string;
price: number;
quantity: number;
}
interface CartStore {
items: CartItem[];
addItem: (item: Omit<CartItem, 'quantity'>) => void;
removeItem: (productId: string) => void;
updateQuantity: (productId: string, quantity: number) => void;
clearCart: () => void;
total: () => number;
}
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
addItem: (item) =>
set((state) => {
const existing = state.items.find((i) => i.productId === item.productId);
if (existing) {
return {
items: state.items.map((i) =>
i.productId === item.productId
? { ...i, quantity: i.quantity + 1 }
: i
),
};
}
return { items: [...state.items, { ...item, quantity: 1 }] };
}),
removeItem: (productId) =>
set((state) => ({
items: state.items.filter((i) => i.productId !== productId),
})),
updateQuantity: (productId, quantity) =>
set((state) => ({
items:
quantity <= 0
? state.items.filter((i) => i.productId !== productId)
: state.items.map((i) =>
i.productId === productId ? { ...i, quantity } : i
),
})),
clearCart: () => set({ items: [] }),
total: () =>
get().items.reduce((sum, item) => sum + item.price * item.quantity, 0),
}),
{
name: 'cart-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// Usage
function CartScreen() {
const { items, removeItem, updateQuantity, clearCart, total } = useCartStore();
return (
<View>
<FlatList
data={items}
keyExtractor={(item) => item.productId}
renderItem={({ item }) => (
<CartItem
item={item}
onRemove={() => removeItem(item.productId)}
onUpdateQuantity={(qty) => updateQuantity(item.productId, qty)}
/>
)}
/>
<Text>Total: ${total().toFixed(2)}</Text>
<Button title="Clear Cart" onPress={clearCart} />
</View>
);
}
// Selective subscriptions for performance
function CartBadge() {
const itemCount = useCartStore((state) =>
state.items.reduce((sum, item) => sum + item.quantity, 0)
);
if (itemCount === 0) return null;
return <Badge value={itemCount} />;
}
5. React Native MMKV – Ultra-Fast Storage
MMKV is a fast key-value storage library that’s significantly faster than AsyncStorage.
# Installation
npm install react-native-mmkv
// storage.ts
import { MMKV } from 'react-native-mmkv';
export const storage = new MMKV();
// Simple key-value operations
storage.set('user.token', 'abc123');
const token = storage.getString('user.token');
storage.set('user.onboarded', true);
const onboarded = storage.getBoolean('user.onboarded');
// Objects (serialized automatically)
const user = { id: '1', name: 'John' };
storage.set('user.data', JSON.stringify(user));
const userData = JSON.parse(storage.getString('user.data') || 'null');
// Zustand with MMKV
import { StateStorage } from 'zustand/middleware';
const zustandStorage: StateStorage = {
getItem: (name) => {
const value = storage.getString(name);
return value ?? null;
},
setItem: (name, value) => {
storage.set(name, value);
},
removeItem: (name) => {
storage.delete(name);
},
};
// Use with persist middleware
export const useAuthStore = create<AuthStore>()(
persist(
(set) => ({
// ... store implementation
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => zustandStorage),
}
)
);
6. React Native Skia
For custom graphics, charts, and creative animations, Skia unlocks native GPU rendering directly from JavaScript.
# Installation
npm install @shopify/react-native-skia
// Custom Gradient Background
import { Canvas, LinearGradient, Rect, vec } from '@shopify/react-native-skia';
function GradientBackground() {
return (
<Canvas style={{ flex: 1 }}>
<Rect x={0} y={0} width={400} height={800}>
<LinearGradient
start={vec(0, 0)}
end={vec(400, 800)}
colors={['#4c669f', '#3b5998', '#192f6a']}
/>
</Rect>
</Canvas>
);
}
// Animated Ring Progress
import { Canvas, Path, Skia, useValue, runTiming } from '@shopify/react-native-skia';
function RingProgress({ progress }: { progress: number }) {
const radius = 100;
const strokeWidth = 12;
const path = Skia.Path.Make();
path.addArc(
{
x: 50,
y: 50,
width: radius * 2,
height: radius * 2,
},
-90,
360 * progress
);
return (
<Canvas style={{ width: 250, height: 250 }}>
{/* Background ring */}
<Path
path={createCirclePath(radius)}
style="stroke"
strokeWidth={strokeWidth}
color="#e0e0e0"
/>
{/* Progress ring */}
<Path
path={path}
style="stroke"
strokeWidth={strokeWidth}
color="#007AFF"
strokeCap="round"
/>
</Canvas>
);
}
7. Expo SDK and Modules
Even if you’re not using Expo’s managed workflow, individual Expo modules provide well-maintained native functionality.
# Popular Expo modules (work in any React Native project)
npx expo install expo-camera
npx expo install expo-location
npx expo install expo-notifications
npx expo install expo-secure-store
npx expo install expo-image-picker
// Camera with permissions
import { Camera, CameraType } from 'expo-camera';
import { useState, useRef } from 'react';
function CameraScreen() {
const [permission, requestPermission] = Camera.useCameraPermissions();
const [type, setType] = useState(CameraType.back);
const cameraRef = useRef<Camera>(null);
if (!permission) return <View />;
if (!permission.granted) {
return (
<View style={styles.container}>
<Text>We need camera permission</Text>
<Button onPress={requestPermission} title="Grant Permission" />
</View>
);
}
const takePicture = async () => {
if (cameraRef.current) {
const photo = await cameraRef.current.takePictureAsync();
console.log(photo.uri);
}
};
return (
<View style={styles.container}>
<Camera style={styles.camera} type={type} ref={cameraRef}>
<View style={styles.buttonContainer}>
<TouchableOpacity
style={styles.button}
onPress={() =>
setType((current) =>
current === CameraType.back ? CameraType.front : CameraType.back
)
}
>
<Text style={styles.text}>Flip</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.captureButton} onPress={takePicture}>
<View style={styles.captureInner} />
</TouchableOpacity>
</View>
</Camera>
</View>
);
}
8. FlashList – High-Performance Lists
FlashList from Shopify is a drop-in replacement for FlatList with significantly better performance for large lists.
# Installation
npm install @shopify/flash-list
import { FlashList } from '@shopify/flash-list';
function ProductList({ products }: { products: Product[] }) {
return (
<FlashList
data={products}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <ProductCard product={item} />}
estimatedItemSize={150} // Important for performance
numColumns={2}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListEmptyComponent={<EmptyState />}
ListHeaderComponent={<SearchBar />}
/>
);
}
Common Mistakes to Avoid
1. Not using memo for list items
// Wrong: Re-renders on every list update
const ProductCard = ({ product }: { product: Product }) => {
return <View>...</View>;
};
// Correct: Memoized component
const ProductCard = React.memo(({ product }: { product: Product }) => {
return <View>...</View>;
});
2. Inline functions in render
// Wrong: Creates new function on every render
<Button onPress={() => handlePress(item.id)} />
// Correct: Stable reference
const handleItemPress = useCallback((id: string) => {
// handle press
}, []);
<Button onPress={() => handleItemPress(item.id)} />
3. Not cleaning up animations
// Wrong: Animation continues after unmount
useEffect(() => {
scale.value = withRepeat(withTiming(1.2), -1, true);
}, []);
// Correct: Cancel on unmount
useEffect(() => {
scale.value = withRepeat(withTiming(1.2), -1, true);
return () => {
cancelAnimation(scale);
};
}, []);
Related
- Flutter or React Native: Which One Is Better for SaaS Apps?
- Flutter State Management: Provider vs Riverpod
- Best Practices for Flutter Routing and Deep Linking in 2025
Final Thoughts
React Native in 2025 is more powerful than ever, but using the right libraries is what truly takes your app from good to great. Whether you’re building a startup MVP or scaling a production app, these libraries save time, improve performance, and deliver better user experiences.
The ecosystem has matured significantly, with libraries like Reanimated 3 and Skia pushing the boundaries of what’s possible in cross-platform development. Combined with modern state management solutions like TanStack Query and Zustand, you can build apps that rival native performance while maintaining a single codebase.
1 Comment