React Native

React Native Libraries You Should Know in 2025

React Native Libraries

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);
  };
}, []);

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

Leave a Comment