Technology /
Tools
Why choose React Native for mobile app development?
Cross-Platform Development
React Native allows developers to write a single codebase for both iOS and Android platforms, significantly reducing development time and effort.
This cross-platform compatibility eliminates the need to build and maintain separate native apps for each operating system, making it an ideal choice for businesses looking to save costs while reaching a broader audience.
When combined with Expo, the setup process becomes even simpler. Expo provides a managed workflow that eliminates the need for configuring native build environments—no more installing Xcode or Android Studio just to get started.
Native-Like Performance
Unlike some other frameworks that rely on web views, React Native uses a bridge to communicate between JavaScript and native code. This allows it to render components using native APIs, ensuring apps deliver a native-like performance and feel.
Animations, gestures, and transitions are smooth, making it nearly indistinguishable from apps developed using native languages like Swift or Kotlin.
Faster Development Cycles with Expo Tools
React Native is known for its hot reloading and live reloading, which let developers see changes in real time. Expo enhances this experience with the Expo Go app.
Instant Preview on Real Devices: The Expo Go app allows developers to preview their apps on a real iOS or Android device without building native binaries. Simply scan a QR code, and the app runs instantly on your device.
Time-Saving for Animations: When tweaking animations or fine-tuning transitions, the ability to instantly preview changes on a device is invaluable. It reduces the back-and-forth debugging process that can slow down development.
Powerful Built-In Features with Expo
Expo comes with a range of built-in modules that handle common mobile development tasks without requiring additional native code or configuration. This means you can achieve more while keeping your workflow simple.
Examples of Expo’s Built-In Capabilities:
Camera and Media: Use the expo-camera library to access the device's camera for features like image capture or barcode scanning.
Device Sensors: Integrate accelerometers, gyroscopes, and other sensors with ease.
Push Notifications: The Expo Notifications API allows you to send and receive push notifications without writing native code.
Over-the-Air (OTA) Updates: One of Expo's standout features is its OTA updates, which allow developers to push changes directly to production apps without needing users to download a new version from the App Store or Google Play.
How OTA Updates Work
With Expo, you can make updates to your app’s JavaScript code and assets, then instantly deliver them to users. This eliminates the traditional update cycle where users must manually download updates via app stores.
Benefits of OTA Updates:
Faster Deployment: Push fixes, new features, or content updates in real-time.
Improved User Experience: Ensure users always have the latest version without prompting them for updates.
No App Store Delays: Avoid waiting for app review processes when making non-native changes.
Example Use Case
Imagine you spot a bug in your app after release. Instead of waiting days for a new app version to be approved by the app stores, you can fix the bug and deliver the update to all users instantly. This feature is particularly useful for content updates, UI tweaks, and other non-native changes.
Cost-Effective and Scalable
React Native is already a cost-effective solution, allowing teams to maintain a single codebase for both iOS and Android. Expo further enhances this by reducing the time spent on native configurations, making it ideal for startups and smaller teams.
Additionally, Expo’s EAS Build (Expo Application Services) simplifies building and deploying apps. Whether you need to generate an APK for Android or a build for iOS, EAS handles it all with minimal configuration, saving you time and effort.
Community and Ecosystem
React Native and Expo benefit from active, thriving developer communities, which translate to faster problem-solving and continuous improvements.
This means quicker resolutions to any issues and access to countless pre-built libraries, saving time and development costs.
With Expo's extensive documentation and tools, many workflows are streamlined, eliminating the need to write everything from scratch. This vibrant ecosystem ensures your app stays up-to-date and benefits from the latest advancements in mobile development.
Building High-Performance Animations with React Native Reanimated
Animations play a crucial role in delivering an engaging user experience. React Native supports animations natively, and when combined with libraries like react-native-reanimated, the possibilities are endless.
Swipe-to-Delete Animation Example
Here’s a practical example: a shopping list demo app with a swipe-to-delete gesture. This demonstrates how React Native Reanimated and Gesture Handler work together to create seamless animations.
Key Features
Native-Thread Animations: Animations run on the native thread, ensuring lag-free performance.
Interactive Gestures: Gesture detection is handled smoothly, allowing for responsive swipe actions.
Declarative APIs: Simplify the creation of animations and ensure maintainability.
Code Walkthrough
Main List Component
The list is built using the FlashList component, with each item rendered using a ListItem component:
import React, { useCallback, useRef } from "react";
import { useState } from "react";
import { RefreshControl, LayoutAnimation } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { FlashList } from "@shopify/flash-list";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import ListItem from "@/components/ListItem";
interface ShoppingItem {
id: number;
name: string;
quantity: number;
category: string;
}
const sampleItems: ShoppingItem[] = [
{ id: 1, name: "Milk", quantity: 2, category: "Dairy" },
{ id: 2, name: "Bread", quantity: 1, category: "Bakery" },
{ id: 3, name: "Eggs", quantity: 12, category: "Dairy" },
{ id: 4, name: "Bananas", quantity: 6, category: "Produce" },
{ id: 5, name: "Chicken Breast", quantity: 1, category: "Meat" },
{ id: 6, name: "Tomatoes", quantity: 4, category: "Produce" },
{ id: 7, name: "Pasta", quantity: 2, category: "Pantry" },
{ id: 8, name: "Cheese", quantity: 1, category: "Dairy" },
{ id: 9, name: "Rice", quantity: 1, category: "Pantry" },
{ id: 10, name: "Apples", quantity: 5, category: "Produce" },
];
export default function HomeScreen() {
const insets = useSafeAreaInsets();
const [refreshing, setRefreshing] = useState<boolean>(false);
const list = useRef<FlashList<number> | null>(null);
const [items, setItems] = useState<ShoppingItem[]>(sampleItems);
const handleDelete = useCallback(
(index: number) => {
setTimeout(() => {
setItems((currentItems) => currentItems.filter((_, i) => i !== index));
// This must be called before `LayoutAnimation.configureNext` in order for the animation to run properly.
list?.current?.prepareForLayoutAnimationRender();
// After removing the item, we can start the animation.
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
list?.current?.recordInteraction();
}, 128);
},
[list?.current]
);
const renderItem = useCallback(
({ item, index }: { item: any; index: number }) => {
return <ListItem onDelete={handleDelete} item={item} index={index} />;
},
[handleDelete]
);
return (
<GestureHandlerRootView>
<FlashList
removeClippedSubviews
ref={list}
extraData={items}
contentInsetAdjustmentBehavior="automatic"
automaticallyAdjustContentInsets
keyExtractor={(item, index) => index.toString()}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={() => {
setRefreshing(true);
setTimeout(() => {
setItems(sampleItems);
setRefreshing(false);
}, 1000);
}}
/>
}
estimatedItemSize={80}
contentContainerStyle={{
paddingTop: insets.top,
paddingHorizontal: 24,
backgroundColor: "#ffffff",
}}
data={items}
renderItem={renderItem}
/>
</GestureHandlerRootView>
);
}
List Item with Swipe-to-Delete Gesture
The ListItem component implements the swipe gesture using react-native-gesture-handler and animates the deletion with react-native-reanimated:
import { AntDesign } from "@expo/vector-icons";
import React from "react";
import { useEffect } from "react";
import { Alert, Pressable, useWindowDimensions, Text } from "react-native";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, {
runOnJS,
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
interface ShoppingItem {
id: number;
name: string;
quantity: number;
category: string;
}
// Memoized ListItem to optimize performance.
const ListItem = React.memo(
({
index,
item,
onDelete,
}: {
index: number;
item: ShoppingItem;
onDelete: (index: number, showSuccessAlert?: boolean) => void;
}) => {
const position = useSharedValue(0);
const actionButtonWidth = useSharedValue(0);
const { width } = useWindowDimensions();
const THRESHOLDX = -width * 0.32;
// Gesture for detecting swiping actions.
const panGesture = Gesture.Pan()
.onStart((e) => {
if (e.translationX <= 0) {
position.value = e.translationX;
if (e.translationX <= THRESHOLDX) {
actionButtonWidth.value = THRESHOLDX;
} else {
actionButtonWidth.value = e.translationX;
}
}
})
.onUpdate((e) => {
if (e.translationX <= 0) {
position.value = e.translationX;
if (e.translationX <= THRESHOLDX) {
actionButtonWidth.value = THRESHOLDX;
} else {
actionButtonWidth.value = e.translationX;
}
}
})
.onEnd((e) => {
if (e.translationX < THRESHOLDX) {
position.value = withTiming(THRESHOLDX - 8);
} else {
actionButtonWidth.value = withTiming(0);
position.value = withTiming(0);
}
})
.activeOffsetX([-8, 8]);
// Style for the swiping animation.
const panAnimation = useAnimatedStyle(() => {
return {
transform: [{ translateX: position.value }],
};
}, []);
// Style for the delete button animation.
const deleteAnimation = useAnimatedStyle(() => {
return {
width: Math.abs(actionButtonWidth.value),
};
}, []);
// Handles item deletion after confirmation.
const handleDelete = () => {
Alert.alert(
"Delete Item",
`Are you sure you want to delete item ${item?.name}?`,
[
{
text: "Cancel",
style: "cancel",
onPress: () => {
// Reset the item position when cancelled
actionButtonWidth.value = withTiming(0);
position.value = withTiming(0);
},
},
{
text: "Delete",
style: "destructive",
onPress: () => {
actionButtonWidth.value = withTiming(0, { duration: 300 });
position.value = withTiming(
-width,
{
duration: 300,
},
() => {
runOnJS(onDelete)(index);
}
);
},
},
]
);
};
useEffect(() => {
// Reset value when id changes (view was recycled for another item)
position.value = 0;
actionButtonWidth.value = 0;
}, [item.id, position]);
return (
<GestureDetector gesture={panGesture}>
<Animated.View
style={{
flexDirection: "row",
marginVertical: 8,
width: "100%",
alignItems: "center",
position: "relative",
}}
>
{/* Swipable item */}
<Animated.View
style={[
panAnimation,
{
width: "100%",
borderRadius: 8,
paddingHorizontal: 16,
paddingVertical: 24,
backgroundColor: "#ffffff",
shadowColor: "#000000",
shadowOffset: {
width: 0,
height: 0,
},
shadowOpacity: 0.2,
shadowRadius: 16,
elevation: 12,
alignItems: "center",
justifyContent: "center",
},
]}
>
<Text>{item?.name}</Text>
</Animated.View>
{/* Delete button */}
<Animated.View
style={[
deleteAnimation,
{
height: "100%",
backgroundColor: "#e63946",
borderRadius: 8,
alignItems: "center",
position: "absolute",
right: 0,
marginLeft: 8,
justifyContent: "center",
transformOrigin: "right",
},
]}
>
<Pressable
onPress={handleDelete}
style={({ pressed }) => [
{
width: "100%",
height: "100%",
alignItems: "center",
justifyContent: "center",
opacity: pressed ? 0.24 : 1,
},
]}
>
<AntDesign name="delete" size={24} color="#f1faee" />
</Pressable>
</Animated.View>
</Animated.View>
</GestureDetector>
);
}
);
export default ListItem;
Live Demonstration
Here’s a video showing the swipe-to-delete animation in action on iOS and Android :