Toolmingo
Guides16 min read

React Native Developer Roadmap 2025 (Step-by-Step Guide)

The complete React Native developer roadmap for 2025 — from JavaScript and React fundamentals through navigation, state management, native modules, testing, and publishing to both app stores.

React Native lets you build real iOS and Android apps in JavaScript with ~95% shared code between platforms. It powers apps at Meta, Microsoft, Shopify, and thousands of startups. This roadmap shows exactly what to learn, in what order, and what to build along the way.

At a glance

Phase Topic Timeline
0 Prerequisites: JavaScript + React Weeks 1–4
1 React Native core Weeks 5–7
2 Styling & layouts Weeks 8–9
3 Navigation Weeks 10–11
4 State management Weeks 12–14
5 Networking & APIs Weeks 15–16
6 Device features & native modules Weeks 17–19
7 Testing Weeks 20–22
8 Performance Weeks 23–24
9 CI/CD & publishing Weeks 25–28
10 Advanced topics Weeks 29–36

Phase 0 — Prerequisites: JavaScript + React (Weeks 1–4)

React Native is JavaScript + React. Skip this if you already know both.

JavaScript you must know

// Arrow functions, destructuring, spread
const greet = ({ name, age = 25 }) => `Hello ${name}, ${age}`;

// Array methods
const doubled = [1, 2, 3].map(n => n * 2);
const evens = [1, 2, 3, 4].filter(n => n % 2 === 0);

// Promises and async/await
async function fetchUser(id) {
  const res = await fetch(`https://api.example.com/users/${id}`);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
}

// Modules
import { useState } from 'react';
export default function App() { /* ... */ }

React concepts you must know

Concept Why it matters in RN
Functional components Everything in RN is functional
useState Local UI state
useEffect Side effects, data fetching
useRef Refs to native views
Props + children Component composition
Custom hooks Reusable logic across screens
Context API Light global state (auth, theme)

Phase 1 — React Native core (Weeks 5–7)

Setting up your environment

Expo (recommended for beginners)

npx create-expo-app MyApp
cd MyApp
npx expo start

React Native CLI (for advanced native work)

npx @react-native-community/cli@latest init MyApp
cd MyApp
npx react-native run-android   # or run-ios
Approach Pros Cons
Expo managed Fast setup, OTA updates, no Xcode/AS needed Limited native APIs
Expo bare Expo DX + full native access Larger project
React Native CLI Full control Complex setup, slow start

Core components

import {
  View, Text, TextInput, Button, TouchableOpacity,
  FlatList, ScrollView, Image, StyleSheet, Platform
} from 'react-native';

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Hello, React Native</Text>
      <TouchableOpacity style={styles.btn} onPress={() => alert('Tap!')}>
        <Text style={styles.btnText}>Press me</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
  title:     { fontSize: 24, fontWeight: 'bold' },
  btn:       { backgroundColor: '#6366f1', padding: 12, borderRadius: 8, marginTop: 16 },
  btnText:   { color: '#fff', fontWeight: '600' },
});

Core component cheat sheet

Component Web equivalent Notes
View div Basic layout container
Text p, span ALL text must be in <Text>
TextInput input onChangeText, value
Image img source={{ uri }} or require()
ScrollView div with overflow Renders all children at once
FlatList virtualized list Use for long lists — lazy render
SectionList grouped list Like FlatList with section headers
TouchableOpacity button Fades on press
Pressable button More control over press states
Modal modal/dialog Full-screen overlay
ActivityIndicator spinner Loading state
Switch checkbox/toggle Boolean toggle

Phase 2 — Styling & layouts (Weeks 8–9)

React Native uses a subset of CSS properties via StyleSheet. No real CSS — no classes, no cascade.

Flexbox in React Native

Flexbox is the only layout system. Default flexDirection is column (not row like web).

// Horizontal centering
<View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
  <Text>Left</Text>
  <Text>Right</Text>
</View>

// Fill remaining space
<View style={{ flex: 1 }}>  {/* takes all remaining space */}
  <Text>Fills the screen</Text>
</View>

Platform-specific styles

import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  shadow: Platform.select({
    ios: {
      shadowColor: '#000',
      shadowOpacity: 0.15,
      shadowRadius: 8,
      shadowOffset: { width: 0, height: 2 },
    },
    android: {
      elevation: 4,
    },
  }),
});

Responsive design

import { Dimensions, useWindowDimensions } from 'react-native';

// Static (doesn't update on orientation change)
const { width, height } = Dimensions.get('window');

// Dynamic (updates on rotate — preferred)
function MyComponent() {
  const { width, height } = useWindowDimensions();
  const isLandscape = width > height;

  return (
    <View style={{ flexDirection: isLandscape ? 'row' : 'column' }}>
      {/* ... */}
    </View>
  );
}

Phase 3 — Navigation (Weeks 10–11)

React Navigation is the de facto standard.

npm install @react-navigation/native
npx expo install react-native-screens react-native-safe-area-context

# Stack navigator
npm install @react-navigation/native-stack

# Tab navigator
npm install @react-navigation/bottom-tabs

# Drawer navigator
npm install @react-navigation/drawer react-native-gesture-handler react-native-reanimated

Stack navigator

import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} options={{ title: 'Item Details' }} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

// Navigate
function HomeScreen({ navigation }) {
  return (
    <TouchableOpacity onPress={() => navigation.navigate('Details', { id: 42 })}>
      <Text>Go to details</Text>
    </TouchableOpacity>
  );
}

// Receive params
function DetailsScreen({ route }) {
  const { id } = route.params;
  return <Text>Item {id}</Text>;
}

Bottom tab navigator

import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';

const Tab = createBottomTabNavigator();

function AppTabs() {
  return (
    <Tab.Navigator screenOptions={({ route }) => ({
      tabBarIcon: ({ color, size }) => {
        const icons = { Home: 'home', Profile: 'person', Settings: 'settings' };
        return <Ionicons name={icons[route.name]} size={size} color={color} />;
      },
    })}>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Profile" component={ProfileScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

Navigation patterns

Pattern Navigator Use case
Stack native-stack Screen-to-screen flow (push/pop)
Tab bottom-tabs Top-level app sections
Drawer drawer Side menu navigation
Modal Stack with presentation: 'modal' Full-screen overlays
Nested Any combination Tab with nested stacks

Phase 4 — State management (Weeks 12–14)

When to use what

State type Solution
Local UI state useState
Shared simple state Context + useReducer
Complex global state Zustand / Redux Toolkit
Server state + caching React Query / SWR
Form state React Hook Form

Zustand (recommended for most apps)

npm install zustand
import { create } from 'zustand';

interface CartStore {
  items: { id: number; qty: number }[];
  add: (id: number) => void;
  remove: (id: number) => void;
  total: () => number;
}

const useCart = create<CartStore>((set, get) => ({
  items: [],
  add: (id) => set(state => ({
    items: state.items.find(i => i.id === id)
      ? state.items.map(i => i.id === id ? { ...i, qty: i.qty + 1 } : i)
      : [...state.items, { id, qty: 1 }],
  })),
  remove: (id) => set(state => ({ items: state.items.filter(i => i.id !== id) })),
  total: () => get().items.reduce((sum, i) => sum + i.qty, 0),
}));

// In a component
function CartIcon() {
  const total = useCart(s => s.total());
  return <Text>{total} items</Text>;
}

Redux Toolkit (enterprise / large teams)

npm install @reduxjs/toolkit react-redux
import { createSlice, configureStore } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: state => { state.value += 1; },
    decrement: state => { state.value -= 1; },
  },
});

export const store = configureStore({ reducer: { counter: counterSlice.reducer } });

Phase 5 — Networking & APIs (Weeks 15–16)

Fetch with error handling

async function apiFetch<T>(url: string, options?: RequestInit): Promise<T> {
  const res = await fetch(url, {
    ...options,
    headers: { 'Content-Type': 'application/json', ...options?.headers },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
  return res.json();
}

React Query (best practice for server state)

npm install @tanstack/react-query
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <UserList />
    </QueryClientProvider>
  );
}

function UserList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => apiFetch<User[]>('https://api.example.com/users'),
  });

  if (isLoading) return <ActivityIndicator />;
  if (error)    return <Text>Error: {error.message}</Text>;

  return (
    <FlatList
      data={data}
      keyExtractor={u => String(u.id)}
      renderItem={({ item }) => <Text>{item.name}</Text>}
    />
  );
}

Offline storage

Package Use case
@react-native-async-storage/async-storage Key-value, small data
expo-sqlite / react-native-sqlite-storage Structured relational data
react-native-mmkv Fast key-value (10× AsyncStorage)
WatermelonDB Large offline-first databases
import AsyncStorage from '@react-native-async-storage/async-storage';

await AsyncStorage.setItem('token', jwt);
const token = await AsyncStorage.getItem('token');
await AsyncStorage.removeItem('token');

Phase 6 — Device features & native modules (Weeks 17–19)

Expo modules (zero native config)

Feature Package
Camera expo-camera
Location expo-location
Push notifications expo-notifications
File system expo-file-system
Image picker expo-image-picker
Secure storage expo-secure-store
Haptics expo-haptics
Sensors expo-sensors
Biometrics expo-local-authentication
Contacts expo-contacts

Location example

import * as Location from 'expo-location';

async function getCurrentLocation() {
  const { status } = await Location.requestForegroundPermissionsAsync();
  if (status !== 'granted') {
    alert('Location permission denied');
    return;
  }
  const loc = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.High });
  return { lat: loc.coords.latitude, lng: loc.coords.longitude };
}

Push notifications with Expo

import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';

async function registerForPushNotifications() {
  if (!Device.isDevice) return null; // must run on real device

  const { status: existingStatus } = await Notifications.getPermissionsAsync();
  const finalStatus = existingStatus === 'granted'
    ? existingStatus
    : (await Notifications.requestPermissionsAsync()).status;

  if (finalStatus !== 'granted') return null;

  const token = (await Notifications.getExpoPushTokenAsync()).data;
  return token; // send this to your backend
}

Phase 7 — Testing (Weeks 20–22)

Testing pyramid for React Native

Level Tool What to test
Unit Jest Pure functions, hooks, store logic
Component React Native Testing Library UI rendering, interactions
E2E Detox / Maestro Full user flows on real device/emulator

Jest + React Native Testing Library

npm install --save-dev @testing-library/react-native jest-expo
import { render, screen, fireEvent } from '@testing-library/react-native';
import Counter from './Counter';

test('increments count on press', () => {
  render(<Counter />);
  const btn = screen.getByText('+');
  fireEvent.press(btn);
  expect(screen.getByText('1')).toBeTruthy();
});

test('shows loading state', async () => {
  render(<UserProfile userId={1} />);
  expect(screen.getByTestId('spinner')).toBeTruthy();
  await screen.findByText('John Doe');
});

Mocking native modules

// __mocks__/@react-native-async-storage/async-storage.ts
const store: Record<string, string> = {};
export default {
  getItem: jest.fn(key => Promise.resolve(store[key] ?? null)),
  setItem: jest.fn((key, val) => { store[key] = val; return Promise.resolve(); }),
  removeItem: jest.fn(key => { delete store[key]; return Promise.resolve(); }),
};

Phase 8 — Performance (Weeks 23–24)

Common performance pitfalls

Problem Symptom Fix
Anonymous functions in render Unnecessary re-renders useCallback
Expensive calculations in render Slow frames useMemo
ScrollView for long lists High memory, slow scroll FlatList with keyExtractor
Missing key on list items Console warning, wrong diffs Add stable key prop
Large images Slow load, memory Resize + cache with expo-image
JS thread blocked Dropped frames, no gesture response Move heavy work to workers / Reanimated
Hermes disabled Slower JS startup Enable Hermes (default in RN 0.70+)

FlatList optimisation

<FlatList
  data={items}
  keyExtractor={item => item.id.toString()}
  renderItem={renderItem}            // memoize with useCallback
  initialNumToRender={10}
  maxToRenderPerBatch={10}
  windowSize={5}
  removeClippedSubviews={true}
  getItemLayout={(_, index) => ({    // only if item height is fixed
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
/>

Animations with Reanimated

npm install react-native-reanimated
import Animated, { useSharedValue, withSpring, useAnimatedStyle } from 'react-native-reanimated';

function SpringBox() {
  const offset = useSharedValue(0);
  const style = useAnimatedStyle(() => ({
    transform: [{ translateY: offset.value }],
  }));

  return (
    <>
      <Animated.View style={[{ width: 60, height: 60, backgroundColor: '#6366f1' }, style]} />
      <Button title="Bounce" onPress={() => { offset.value = withSpring(-100); }} />
    </>
  );
}

Phase 9 — CI/CD & publishing (Weeks 25–28)

EAS Build (Expo Application Services)

npm install -g eas-cli
eas login
eas build:configure

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

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

EAS profiles in eas.json

{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "android": { "buildType": "apk" }
    },
    "production": {
      "autoIncrement": true
    }
  }
}

GitHub Actions for Expo

name: EAS Build
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20 }
      - run: npm ci
      - uses: expo/expo-github-action@v8
        with:
          expo-version: latest
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}
      - run: eas build --platform all --non-interactive

App store requirements

Step iOS Android
Developer account Apple Developer ($99/yr) Google Play ($25 one-time)
Binary format .ipa .aab (preferred) / .apk
Review time 1–3 days Hours–1 day
OTA updates Expo Updates (JS only) Expo Updates (JS only)
Native code updates Full store submission Full store submission

Phase 10 — Advanced topics (Weeks 29–36)

New Architecture (Fabric + JSI)

React Native 0.71+ ships with the new architecture enabled by default. Key improvements:

Old architecture New architecture
Async bridge JSI — synchronous JS ↔ native calls
JSON serialisation overhead Direct memory access
Async layout Synchronous layout with Fabric
UIManager.dispatchViewManagerCommand Direct calls via HostComponent

TypeScript best practices

// Type your navigation params
type RootStackParamList = {
  Home: undefined;
  Details: { id: number; title: string };
  Profile: { userId: string };
};

// Type-safe navigation prop
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
type HomeNavProp = NativeStackNavigationProp<RootStackParamList, 'Home'>;

function HomeScreen({ navigation }: { navigation: HomeNavProp }) {
  navigation.navigate('Details', { id: 1, title: 'Test' }); // fully typed
}

Monorepo with Expo + web

npx create-expo-app --template tabs
# Add Next.js for web in a Turborepo / pnpm workspace

Custom native module (Expo Modules API)

// modules/my-module/src/index.ts
import MyModule from './MyModuleModule';
export function getDeviceName(): string {
  return MyModule.getDeviceName();
}

// iOS: MyModuleModule.swift
// Android: MyModuleModule.kt
// Both implement getDeviceName() returning a String

Full technology map

Language      → JavaScript / TypeScript
Runtime       → React Native (Hermes engine)
UI            → Core components, Expo UI, NativeWind (Tailwind)
Navigation    → React Navigation, Expo Router
State         → Zustand, Redux Toolkit, Context API
Server state  → TanStack Query, SWR
Forms         → React Hook Form + Zod
HTTP          → Fetch, Axios
Storage       → AsyncStorage, MMKV, SQLite, WatermelonDB
Auth          → Clerk, Firebase Auth, Supabase Auth
Push          → Expo Notifications, Firebase FCM
Animations    → Reanimated, Moti, Lottie
Testing       → Jest, RNTL, Detox, Maestro
Build         → Expo EAS, Fastlane
CI/CD         → GitHub Actions, Bitrise, CircleCI
Maps          → react-native-maps, MapLibre

36-week timeline

Weeks Milestone
1–4 JavaScript ES6+ + React hooks solid
5–7 First RN app running on device, core components
8–9 Styled layouts that look good on iOS + Android
10–11 Multi-screen app with tabs + stack
12–14 State management with Zustand
15–16 API data displayed in FlatList with loading/error states
17–19 Camera, location, or push notifications working
20–22 60%+ test coverage on key components
23–24 Smooth animations, no JS thread jank
25–28 App live on TestFlight / Play internal testing
29–36 Production app shipped, TypeScript strict, monorepo

Portfolio project ideas

Project What you practice
Todo app with AsyncStorage State, storage, CRUD
Weather app Geolocation, external API, FlatList
Chat app (Firebase) Real-time listeners, auth, Firestore
E-commerce product list Navigation, React Query, Cart with Zustand
Fitness tracker Device sensors, charts, SQLite
Social feed with camera Camera, image upload, pagination

React Native developer roles & salary

Role Experience US salary
Junior RN Developer 0–2 years $60k–$90k
Mid RN Developer 2–4 years $90k–$130k
Senior RN Developer 4+ years $130k–$170k
Mobile Tech Lead 6+ years $160k–$200k
Staff Mobile Engineer 8+ years $190k–$240k

React Native developers who also know Expo, TypeScript, and CI/CD (EAS) typically command a 10–20% premium over developers who know vanilla RN only.


Common mistakes

Mistake Problem Fix
Using ScrollView for long lists Memory leak on 1000+ items FlatList with keyExtractor
Ignoring TypeScript Runtime crashes from bad types Enable strict: true in tsconfig
Storing secrets in app bundle Secrets exposed by reverse-engineering Backend proxy, never in client
Not testing on real devices iOS/Android differences missed Test on both platforms early
Deep nesting without navigation Hard to debug, back button broken Use React Navigation from day 1
Re-renders every keystroke Laggy text input Controlled inputs + useCallback
Blocking JS thread Dropped frames, unresponsive UI Reanimated worklets, batch state updates
Not memoizing list renderItem Full re-render on every state change useCallback + React.memo

React Native vs Flutter vs Kotlin Multiplatform

Dimension React Native Flutter KMP
Language JavaScript / TypeScript Dart Kotlin
UI rendering Native OS widgets Custom Canvas (Skia) Native OS widgets per platform
Shared code ~95% ~98% Logic only (UI per platform)
Performance Near-native Near-native Native
Learning curve Low (if you know React) Medium High
Ecosystem Huge (npm) Growing Small
Hot reload Yes Yes Partial
Web support React Native Web Yes No
Job market 2025 Large Large Small but growing
Companies Meta, Microsoft, Shopify Google, BMW, Alibaba JetBrains, large enterprises

Frequently asked questions

Should I start with Expo or React Native CLI? Start with Expo. It removes 80% of setup complexity, supports all common native APIs via Expo modules, and lets you publish with EAS Build without Xcode or Android Studio for most use cases. Switch to bare workflow when you need a custom native module that Expo doesn't support.

Do I need to know iOS/Android development? Not to start. You can build and ship a production app with only JavaScript. But as you advance — debugging native crashes, writing custom modules, configuring provisioning profiles — knowledge of Swift/Objective-C (iOS) or Kotlin/Java (Android) becomes valuable.

Is React Native dead / dying? No. Meta actively maintains it, and the New Architecture (Fabric + JSI) shipped in 0.71+, making it significantly faster. Shopify, Microsoft, and hundreds of Fortune 500 companies use it in production.

React Native vs Flutter — which should I learn? If you already know React/JavaScript, React Native is faster to learn and shares a huge npm ecosystem. If you're starting fresh and want best-in-class UI performance and a unified rendering engine, Flutter is excellent. Both are solid choices with strong job markets.

Can React Native build for web? Yes, via react-native-web. Some teams maintain a single codebase for iOS, Android, and web. Expo also has first-class web support. However, the web story is secondary — if web is your primary target, use Next.js or Remix instead.

What's the best way to handle auth? Clerk (@clerk/clerk-expo) or Supabase Auth are the fastest paths to production-ready auth with social login, MFA, and session management. For full control, implement JWT + refresh token rotation yourself with Axios interceptors and MMKV storage.

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools