Build a Cross-Platform App with React Native and Expo in 2026: From Zero to App Store

React Native with Expo has become the go-to framework for building production-ready mobile apps in 2026. With Expo SDK 53, the New Architecture enabled by default, and seamless EAS Build integration, you can ship polished iOS and Android apps from a single TypeScript codebase faster than ever. In this guide, we’ll build a practical task manager app step by step, covering navigation, state management, and native device features.

Why React Native + Expo in 2026?

The mobile development landscape has shifted dramatically. Expo is no longer a “beginner tool” — it’s the recommended way to build React Native apps, even by the React Native team. Here’s what makes it compelling:

  • Expo Router v4 — file-based routing inspired by Next.js, with deep linking out of the box
  • New Architecture — JSI-based bridge replacement for near-native performance
  • EAS Build & Submit — cloud builds and app store submissions without Xcode or Android Studio
  • Expo Modules API — write native modules in Swift/Kotlin with a clean TypeScript interface
  • Universal apps — target iOS, Android, and web from the same codebase

Setting Up Your Project

First, create a new Expo project with the latest template:

npx create-expo-app@latest TaskFlow --template tabs
cd TaskFlow
npx expo start

This scaffolds a project with Expo Router, TypeScript, and tab navigation preconfigured. Your project structure looks like this:

TaskFlow/
├── app/
│   ├── (tabs)/
│   │   ├── index.tsx        # Home tab
│   │   ├── settings.tsx     # Settings tab
│   │   └── _layout.tsx      # Tab layout
│   ├── task/
│   │   └── [id].tsx         # Dynamic task detail route
│   └── _layout.tsx          # Root layout
├── components/
├── hooks/
└── constants/

Building the Task List Screen

Let’s build the main task list using React Native’s FlatList and Zustand for state management. First, install Zustand:

npx expo install zustand

Create a store at store/taskStore.ts:

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

interface Task {
  id: string;
  title: string;
  completed: boolean;
  priority: 'low' | 'medium' | 'high';
  createdAt: number;
}

interface TaskStore {
  tasks: Task[];
  addTask: (title: string, priority: Task['priority']) => void;
  toggleTask: (id: string) => void;
  deleteTask: (id: string) => void;
}

export const useTaskStore = create<TaskStore>()(
  persist(
    (set) => ({
      tasks: [],
      addTask: (title, priority) =>
        set((state) => ({
          tasks: [
            {
              id: Date.now().toString(),
              title,
              completed: false,
              priority,
              createdAt: Date.now(),
            },
            ...state.tasks,
          ],
        })),
      toggleTask: (id) =>
        set((state) => ({
          tasks: state.tasks.map((t) =>
            t.id === id ? { ...t, completed: !t.completed } : t
          ),
        })),
      deleteTask: (id) =>
        set((state) => ({
          tasks: state.tasks.filter((t) => t.id !== id),
        })),
    }),
    {
      name: 'task-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

Now build the task list screen at app/(tabs)/index.tsx:

import { FlatList, Pressable, StyleSheet, View } from 'react-native';
import { Text } from '@/components/Themed';
import { useTaskStore } from '@/store/taskStore';
import { Link } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';

const priorityColors = {
  high: '#ef4444',
  medium: '#f59e0b',
  low: '#22c55e',
};

export default function HomeScreen() {
  const { tasks, toggleTask, deleteTask } = useTaskStore();

  return (
    <View style={styles.container}>
      <FlatList
        data={tasks}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <Pressable
            style={styles.taskRow}
            onPress={() => toggleTask(item.id)}
          >
            <View style={[
              styles.priorityDot,
              { backgroundColor: priorityColors[item.priority] }
            ]} />
            <Text style={[
              styles.taskTitle,
              item.completed && styles.completed
            ]}>
              {item.title}
            </Text>
            <Pressable onPress={() => deleteTask(item.id)}>
              <Ionicons name="trash-outline" size={20} color="#888" />
            </Pressable>
          </Pressable>
        )}
        ListEmptyComponent={
          <Text style={styles.empty}>No tasks yet. Tap + to add one.</Text>
        }
      />
      <Link href="/task/new" asChild>
        <Pressable style={styles.fab}>
          <Ionicons name="add" size={28} color="#fff" />
        </Pressable>
      </Link>
    </View>
  );
}

Adding Haptic Feedback and Notifications

Native features are where Expo shines. Let’s add haptic feedback when completing tasks and schedule local notifications for reminders:

npx expo install expo-haptics expo-notifications

Enhance the toggle function with haptics:

import * as Haptics from 'expo-haptics';

const handleToggle = (id: string) => {
  toggleTask(id);
  Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
};

Schedule a reminder notification:

import * as Notifications from 'expo-notifications';

async function scheduleReminder(taskTitle: string, minutes: number) {
  await Notifications.scheduleNotificationAsync({
    content: {
      title: 'Task Reminder 📋',
      body: `Don't forget: ${taskTitle}`,
      sound: true,
    },
    trigger: {
      type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL,
      seconds: minutes * 60,
    },
  });
}

Swipe-to-Delete with Reanimated

For a polished UX, add swipe gestures using react-native-gesture-handler and react-native-reanimated (both included with Expo):

import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  runOnJS,
} from 'react-native-reanimated';

function SwipeableTask({ task, onDelete }) {
  const translateX = useSharedValue(0);

  const panGesture = Gesture.Pan()
    .onUpdate((e) => {
      if (e.translationX < 0) {
        translateX.value = e.translationX;
      }
    })
    .onEnd((e) => {
      if (e.translationX < -120) {
        translateX.value = withTiming(-400, {}, () => {
          runOnJS(onDelete)(task.id);
        });
      } else {
        translateX.value = withTiming(0);
      }
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: translateX.value }],
  }));

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View style={[styles.taskRow, animatedStyle]}>
        <Text>{task.title}</Text>
      </Animated.View>
    </GestureDetector>
  );
}

Building and Deploying with EAS

The biggest advantage of Expo in 2026 is EAS (Expo Application Services). No need for Xcode or Android Studio on your machine:

# Install EAS CLI
npm install -g eas-cli

# Configure your project
eas build:configure

# Build for both platforms
eas build --platform all --profile production

# Submit to app stores
eas submit --platform ios
eas submit --platform android

Your eas.json configuration:

{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "ios": { "simulator": true }
    },
    "production": {
      "autoIncrement": true
    }
  },
  "submit": {
    "production": {
      "ios": { "appleId": "your@email.com" },
      "android": { "serviceAccountKeyPath": "./pc-api-key.json" }
    }
  }
}

Performance Tips for 2026

With the New Architecture now stable, here are key performance practices:

  1. Use React.memo and useCallback — prevent unnecessary re-renders in long lists
  2. FlashList over FlatList — Shopify’s @shopify/flash-list renders lists 5x faster
  3. Image optimization — use expo-image instead of the default Image component for caching and blurhash placeholders
  4. Avoid bridge traffic — the New Architecture uses JSI, but batching state updates still matters
  5. Enable Hermes — it’s the default engine now, but verify with global.HermesInternal

Wrapping Up

React Native with Expo in 2026 is a mature, production-ready platform. We’ve built a task manager with persistent state, haptic feedback, notifications, swipe gestures, and cloud deployment — all without touching Xcode or Android Studio. The ecosystem has grown significantly: Expo Router handles navigation elegantly, Zustand keeps state simple, and EAS removes the pain of app store submissions.

For your next steps, explore Expo’s universal links for deep linking, expo-secure-store for sensitive data, and React Native Skia for custom graphics. The gap between native and cross-platform has never been smaller.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Privacy Policy · Contact · Sitemap

© 7Tech – Programming and Tech Tutorials