R
Rishtaara
Knowledge Hub
Technology & IT

React Native Fundamentals: Complete Guide

By Rishtaara Editorial Team40 min read
#React Native#Mobile#Components#Navigation#Hooks

From environment setup to your first complete app — components, Flexbox, hooks, React Navigation, REST APIs, AsyncStorage, and cross-platform patterns with flowcharts and code examples.

What is React Native?

React Native is an open-source framework by Meta that lets you build native mobile apps using JavaScript and React. You write one codebase and deploy to both iOS and Android — each platform renders real native UI components, not a WebView.

Unlike hybrid apps (Cordova, Ionic WebView), React Native maps your <View> to UIView on iOS and android.view on Android. Users get native scroll physics, accessibility, and performance.

React Native is used by Facebook, Instagram, Shopify, Discord, Microsoft, and thousands of startups. It is ideal when you already know React and need mobile + web skill overlap.

Course learning path

How React Native works

Your JavaScript runs on Hermes (or JavaScriptCore) inside the app. React components describe UI; the bridge serializes updates to native views. New Architecture (Fabric + TurboModules) reduces bridge overhead for better performance.

React Native architecture

React Native vs alternatives

  • Flutter — Dart language, own rendering engine; excellent performance, smaller talent pool.
  • Expo — toolchain on top of RN; faster setup, managed builds; can eject to bare RN.
  • Native (Swift/Kotlin) — maximum control; two separate codebases.
  • React Native — best when team knows React; huge npm ecosystem; OTA updates possible.

What you will build

  • Understand project structure, Metro bundler, and dev workflow.
  • Build screens with core components, Flexbox styling, and lists.
  • Manage state with hooks and Context; navigate with React Navigation.
  • Fetch REST APIs, cache with AsyncStorage, handle loading and errors.
  • Ship a complete cross-platform app: auth flow, tabs, and API-driven screens.

Prerequisites

You need Node.js 18+, a code editor (VS Code recommended), and platform SDKs for the devices you target. macOS is required for iOS Simulator; Android emulator runs on Windows, macOS, and Linux.

  • Node.js 18 LTS or 20 LTS — use nvm to manage versions.
  • Watchman (macOS) — file watcher for Metro: brew install watchman.
  • Xcode 15+ (macOS only) — iOS Simulator and App Store builds.
  • Android Studio — SDK, emulator, and platform tools.
  • JDK 17 — required by modern Android Gradle Plugin.

Create your first project

Using React Native CLI (bare workflow)
npx @react-native-community/cli@latest init MyFirstApp
cd MyFirstApp
# iOS (macOS only)
cd ios && pod install && cd ..
npx react-native run-ios
# Android
npx react-native run-android
Using Expo (recommended for beginners)
npx create-expo-app@latest MyFirstApp
cd MyFirstApp
npx expo start
# Press i for iOS simulator, a for Android emulator

Verify your setup

  • Metro bundler starts without errors on npx react-native start.
  • iOS Simulator or Android Emulator launches the default welcome screen.
  • Edit App.tsx — save — app hot-reloads within 2 seconds.
  • React Native Doctor: npx react-native doctor — fixes common issues.
Stuck on Android? Enable USB debugging on physical device or create AVD in Android Studio Device Manager.

Key folders in a bare React Native project

  • App.tsx / index.js — JavaScript entry point registered with AppRegistry.
  • src/ — your components, screens, hooks, services (convention, not required).
  • android/ — Gradle project, AndroidManifest.xml, native Java/Kotlin code.
  • ios/ — Xcode workspace, Info.plist, native Swift/Objective-C code.
  • node_modules/ — npm dependencies.
  • metro.config.js — bundler configuration (assets, transformers).

Metro Bundler explained

Metro is React Native's JavaScript bundler (like webpack for web). It transforms JSX/TypeScript, resolves imports, and serves the bundle to the app in development. In production, a single optimized bundle is embedded in the app.

Fast Refresh preserves component state when you edit code — essential for rapid UI iteration.

Register root component (index.js)
import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";

AppRegistry.registerComponent(appName, () => App);

Recommended src/ structure

Scalable folder layout
src/
  components/     # reusable UI (Button, Card)
  screens/        # full pages (HomeScreen, LoginScreen)
  navigation/     # React Navigation config
  hooks/          # custom hooks (useAuth, useFetch)
  services/       # API clients, storage helpers
  context/        # React Context providers
  types/          # TypeScript interfaces
  utils/          # formatters, validators

Building blocks of every screen

React Native provides primitive components that map to native widgets. You compose them like HTML divs and spans — but use <View> and <Text>, never raw strings outside <Text>.

Component tree example

Essential components

  • View — container for layout (like div). Supports Flexbox, touch handling, accessibility.
  • Text — all text must be inside Text; supports nesting and styling.
  • Image — local (require) or remote (uri); always set dimensions or flex.
  • ScrollView — scrollable container for content that does not fit one screen.
  • SafeAreaView — respects notch and home indicator on modern phones.
  • StatusBar — control status bar style (light/dark content).
Basic screen layout
import { View, Text, Image, ScrollView, SafeAreaView } from "react-native";

export function ProfileScreen() {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <ScrollView contentContainerStyle={{ padding: 16 }}>
        <Image
          source={{ uri: "https://example.com/avatar.jpg" }}
          style={{ width: 80, height: 80, borderRadius: 40 }}
        />
        <Text style={{ fontSize: 24, fontWeight: "bold" }}>Jane Developer</Text>
        <Text style={{ color: "#666" }}>React Native engineer</Text>
      </ScrollView>
    </SafeAreaView>
  );
}

StyleSheet and Flexbox

React Native uses JavaScript objects for styles — no CSS files. Flexbox is the default layout system and works the same on iOS and Android. flexDirection defaults to column (unlike web row).

Column flex layout

Key Flexbox properties

  • flexDirection: 'row' | 'column' — main axis direction.
  • justifyContent — align along main axis (center, space-between, flex-end).
  • alignItems — align along cross axis.
  • flex: 1 — grow to fill available space.
  • gap — spacing between children (RN 0.71+).
StyleSheet example
import { StyleSheet, View, Text } from "react-native";

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16, backgroundColor: "#0f172a" },
  card: {
    backgroundColor: "#1e293b",
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
  },
  title: { fontSize: 18, fontWeight: "600", color: "#f1f5f9" },
});

export function Card({ title }: { title: string }) {
  return (
    <View style={styles.card}>
      <Text style={styles.title}>{title}</Text>
    </View>
  );
}

Platform-specific styles

Platform.select and conditional styles
import { Platform, StyleSheet } from "react-native";

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

Handling user interaction

Use Pressable (modern) or TouchableOpacity for buttons and tappable areas. TextInput handles keyboard input. Always provide accessibilityLabel for screen readers.

Pressable button with feedback
import { Pressable, Text, StyleSheet } from "react-native";

export function PrimaryButton({ title, onPress }: { title: string; onPress: () => void }) {
  return (
    <Pressable
      onPress={onPress}
      style={({ pressed }) => [styles.btn, pressed && styles.pressed]}
      accessibilityRole="button"
      accessibilityLabel={title}
    >
      <Text style={styles.label}>{title}</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  btn: { backgroundColor: "#06b6d4", padding: 14, borderRadius: 10, alignItems: "center" },
  pressed: { opacity: 0.85 },
  label: { color: "#fff", fontWeight: "600" },
});
Controlled TextInput
const [email, setEmail] = useState("");
<TextInput
  value={email}
  onChangeText={setEmail}
  placeholder="Email"
  keyboardType="email-address"
  autoCapitalize="none"
  style={styles.input}
/>

State and side effects in functional components

useState stores data that changes over time — taps, form input, API responses. useEffect runs side effects after render — fetching data, subscriptions, timers. Together they replace class component lifecycle methods.

State update flow

useState patterns

Counter and object state
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: "", age: 0 });

// Functional update — safe when new state depends on old
setCount((prev) => prev + 1);

// Object update — always spread, never mutate
setUser((prev) => ({ ...prev, name: "Alex" }));

useEffect patterns

Fetch on mount and cleanup
useEffect(() => {
  let cancelled = false;

  async function load() {
    const res = await fetch("https://api.example.com/posts");
    const data = await res.json();
    if (!cancelled) setPosts(data);
  }
  load();

  return () => { cancelled = true; }; // cleanup on unmount
}, []); // empty deps = run once on mount

Rules of hooks

  • Only call hooks at the top level — not inside loops, conditions, or nested functions.
  • Only call hooks from React function components or custom hooks.
  • Dependency array controls when useEffect re-runs — include all values used inside.

Building reusable UI

Props pass data and callbacks from parent to child. Composition — nesting children inside components — is preferred over inheritance. Build small, focused components and assemble screens from them.

Typed props with TypeScript
type ProductCardProps = {
  title: string;
  price: number;
  imageUrl: string;
  onPress: () => void;
};

export function ProductCard({ title, price, imageUrl, onPress }: ProductCardProps) {
  return (
    <Pressable onPress={onPress} style={styles.card}>
      <Image source={{ uri: imageUrl }} style={styles.image} />
      <Text style={styles.title}>{title}</Text>
      <Text style={styles.price}>₹{price}</Text>
    </Pressable>
  );
}
Composition with children
export function Screen({ children }: { children: React.ReactNode }) {
  return (
    <SafeAreaView style={styles.safe}>
      <View style={styles.content}>{children}</View>
    </SafeAreaView>
  );
}

Efficient long lists

Never map() thousands of items inside ScrollView — it renders everything at once and crashes. FlatList virtualizes: only visible rows mount. SectionList groups data with headers.

FlatList with pull-to-refresh
type Item = { id: string; title: string };

export function PostList({ data, onRefresh }: { data: Item[]; onRefresh: () => void }) {
  const [refreshing, setRefreshing] = useState(false);

  const handleRefresh = async () => {
    setRefreshing(true);
    await onRefresh();
    setRefreshing(false);
  };

  return (
    <FlatList
      data={data}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <PostCard title={item.title} />}
      refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}
      ListEmptyComponent={<Text>No posts yet</Text>}
      contentContainerStyle={{ padding: 16 }}
    />
  );
}

FlatList performance tips

  • Always provide keyExtractor with stable unique IDs.
  • Use getItemLayout when all rows have fixed height — skips measurement.
  • Memoize renderItem with useCallback; wrap row component in React.memo.
  • Avoid anonymous functions in renderItem when possible.

Building forms in React Native

Controlled inputs with useState work for simple forms. For complex forms use react-hook-form or Formik. Validate on submit and show inline errors.

Login form with validation
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errors, setErrors] = useState<Record<string, string>>({});

function validate() {
  const e: Record<string, string> = {};
  if (!email.includes("@")) e.email = "Invalid email";
  if (password.length < 6) e.password = "Min 6 characters";
  setErrors(e);
  return Object.keys(e).length === 0;
}

function handleSubmit() {
  if (!validate()) return;
  // call API...
}

Keyboard handling

  • KeyboardAvoidingView — shifts content when keyboard opens (behavior='padding' on iOS).
  • keyboardShouldPersistTaps='handled' on ScrollView — taps work while keyboard is open.
  • TouchableWithoutFeedback + Keyboard.dismiss() — tap outside to close keyboard.

Screen navigation fundamentals

React Navigation is the standard routing library for React Native. Stack Navigator pushes screens like a deck of cards — with back gestures on iOS and hardware back on Android.

Stack navigation flow

Setup and basic stack

Install React Navigation
npm install @react-navigation/native @react-navigation/native-stack
npm install react-native-screens react-native-safe-area-context
# iOS: cd ios && pod install
Stack navigator
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

export type RootStackParamList = {
  Home: undefined;
  Details: { itemId: string };
};

const Stack = createNativeStackNavigator<RootStackParamList>();

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

// Navigate with typed params:
// navigation.navigate("Details", { itemId: "42" });

Multi-section apps

Bottom tabs suit primary app sections (Home, Search, Profile). Drawer suits many secondary destinations. Nest navigators: tabs inside stack, or stack inside each tab.

Bottom tab structure

Bottom tab navigator

Tab navigator with icons
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";

const Tab = createBottomTabNavigator();

function MainTabs() {
  return (
    <Tab.Navigator screenOptions={{ headerShown: false }}>
      <Tab.Screen name="Home" component={HomeStack} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

Navigation patterns

  • Auth flow: Stack with Login vs MainTabs based on isLoggedIn state.
  • Modal screens: presentation: 'modal' in stack screen options.
  • Deep linking: map URLs to screens for push notification taps.
  • Type-safe params: define ParamList types for every navigator.

Sharing state across screens

Context passes data through the tree without prop drilling. Use for theme, auth user, and locale. For complex global state consider Zustand or Redux Toolkit — but Context is enough for most apps.

AuthContext pattern
type AuthContextType = {
  user: User | null;
  login: (email: string, pass: string) => Promise<void>;
  logout: () => void;
};

const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const login = async (email: string, pass: string) => {
    const res = await api.login(email, pass);
    setUser(res.user);
    await AsyncStorage.setItem("token", res.token);
  };

  const logout = async () => {
    setUser(null);
    await AsyncStorage.removeItem("token");
  };

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuth must be inside AuthProvider");
  return ctx;
}

Networking in React Native

Use fetch (built-in) or axios for HTTP requests. Handle loading, success, and error states explicitly. Never assume the network is fast or available — mobile users switch between WiFi and cellular constantly.

API data flow

API service layer

Typed API client
const BASE_URL = "https://api.example.com";

export async function apiGet<T>(path: string, token?: string): Promise<T> {
  const res = await fetch(BASE_URL + path, {
    headers: {
      "Content-Type": "application/json",
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
    },
  });
  if (!res.ok) throw new Error(`API error ${res.status}`);
  return res.json();
}

// Usage in screen:
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
  apiGet<Post[]>("/posts")
    .then(setPosts)
    .catch((e) => setError(e.message))
    .finally(() => setLoading(false));
}, []);

Best practices

  • Centralize API calls in services/ — screens only call hooks.
  • Use custom hook useFetch(url) to DRY loading/error logic.
  • Set timeout with AbortController for slow networks.
  • Show ActivityIndicator while loading; retry button on error.

Storing data on device

@react-native-async-storage/async-storage is an asynchronous, persistent key-value store. Use for auth tokens, user preferences, and offline cache. For sensitive data prefer react-native-keychain.

AsyncStorage helpers
import AsyncStorage from "@react-native-async-storage/async-storage";

export async function saveToken(token: string) {
  await AsyncStorage.setItem("auth_token", token);
}

export async function getToken() {
  return AsyncStorage.getItem("auth_token");
}

export async function saveCache<T>(key: string, data: T) {
  await AsyncStorage.setItem(key, JSON.stringify(data));
}

export async function loadCache<T>(key: string): Promise<T | null> {
  const raw = await AsyncStorage.getItem(key);
  return raw ? JSON.parse(raw) : null;
}

When to use what

  • AsyncStorage — tokens, settings, small JSON cache (< 6MB per key on Android).
  • Keychain / EncryptedStorage — passwords, refresh tokens, PII.
  • SQLite / WatermelonDB — large structured offline datasets.
  • MMKV — fast key-value alternative for performance-critical reads.

iOS vs Android differences

Most RN code is cross-platform. When platforms diverge — shadows, permissions, back button — use Platform API or separate .ios.tsx / .android.tsx files.

Platform module
import { Platform } from "react-native";

const TAB_BAR_HEIGHT = Platform.OS === "ios" ? 88 : 64;

if (Platform.OS === "android") {
  // Android-only logic
}
Platform-specific files
Icon.ios.tsx   → used on iOS only
Icon.android.tsx → used on Android only
Icon.tsx         → fallback for both

Common platform differences

  • Shadows: shadow* props on iOS, elevation on Android.
  • Back button: Android hardware back — handle with BackHandler or React Navigation.
  • Safe areas: notches, Dynamic Island, navigation bar insets.
  • Permissions: request camera/location at runtime with react-native-permissions.

Working with media

Bundle static images with require() — Metro includes them in the build. Remote images use uri but need explicit width/height. Use vector icons (@expo/vector-icons or react-native-vector-icons) for crisp icons at any size.

Local and remote images
// Local — width/height optional (Metro knows dimensions)
<Image source={require("./assets/logo.png")} />

// Remote — always set dimensions
<Image
  source={{ uri: "https://example.com/photo.jpg" }}
  style={{ width: 300, height: 200 }}
  resizeMode="cover"
/>

// Vector icon
import { Ionicons } from "@expo/vector-icons";
<Ionicons name="home" size={24} color="#06b6d4" />

Finding and fixing bugs

  • Metro terminal — redbox shows JS errors with stack trace; tap to open.
  • React Native DevTools — press j in Metro or shake device → Debug.
  • Flipper / Reactotron — network inspector, AsyncStorage viewer, layout.
  • console.log — works in Metro terminal; remove before production.
  • React DevTools — inspect component tree and props.

Common issues and fixes

  • Red screen 'Unable to resolve module' — wrong import path or missing npm install.
  • White screen — silent JS error; check Metro logs.
  • Network request failed on Android emulator — use 10.0.2.2 instead of localhost.
  • Pod install errors — cd ios && pod deintegrate && pod install.
  • Clear cache: npx react-native start --reset-cache.
Android emulator localhost
iOS Simulator:  http://localhost:3000/api
Android Emulator: http://10.0.2.2:3000/api
Physical device:  http://YOUR_LAN_IP:3000/api

Project: Rishtaara Reader app

Let's architect a complete cross-platform app: login screen, bottom tabs (Home + Profile), API-driven article list, pull-to-refresh, and persisted auth token. This ties together every concept in the course.

App architecture

App structure

Root App.tsx with auth gate
export default function App() {
  return (
    <AuthProvider>
      <NavigationContainer>
        <RootNavigator />
      </NavigationContainer>
    </AuthProvider>
  );
}

function RootNavigator() {
  const { user, loading } = useAuth();
  if (loading) return <SplashScreen />;
  return user ? <MainTabs /> : <AuthStack />;
}
HomeScreen — FlatList + API
export function HomeScreen() {
  const { data, loading, error, refresh } = useArticles();

  if (loading) return <ActivityIndicator size="large" />;
  if (error) return <ErrorView message={error} onRetry={refresh} />;

  return (
    <FlatList
      data={data}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <ArticleCard
          title={item.title}
          excerpt={item.excerpt}
          onPress={() => navigation.navigate("Article", { id: item.id })}
        />
      )}
      refreshControl={<RefreshControl refreshing={loading} onRefresh={refresh} />}
    />
  );
}

Checklist before calling it done

  • Works on both iOS Simulator and Android Emulator.
  • Loading, empty, and error states handled on every API screen.
  • Keyboard does not cover login form inputs.
  • Back navigation works correctly from detail screens.
  • Token persists — app reopens logged in after kill.

You have the fundamentals — what comes next?

  • Animations — React Native Reanimated for 60fps gestures and transitions.
  • Native modules — bridge to Swift/Kotlin when RN APIs are not enough.
  • TypeScript strict mode — catch bugs at compile time across large codebases.
  • Testing — Jest + React Native Testing Library for unit; Detox for E2E.
  • CI/CD — automate builds and store uploads (see our Advanced CI/CD course).
  • Expo Router — file-based navigation if you use Expo SDK 50+.

Practice and continue learning

  • MCQ Quiz: /mcq/react-native-fundamentals-mcq
  • Interview prep: /interview/react-native-fundamentals-interview
  • Advanced CI/CD course: /learn/mobile-development/react-native-advanced-ci-cd
  • Full reference guide: /knowledge/react-native-fundamentals
Build one real app end-to-end — a todo app with auth does not count. Ship something you would use daily.

Key Takeaways

  • React Native renders real native UI from React components — one codebase for iOS and Android.
  • Master View, Text, Flexbox, and FlatList before adding libraries.
  • useState + useEffect + Context cover most app state needs.
  • React Navigation Stack + Tabs is the standard routing pattern.
  • Always handle loading, error, and empty states for API screens.
  • Structure projects with src/screens, components, services, and navigation folders.

Frequently Asked Questions

Expo or React Native CLI for beginners?
Expo is faster to start — no Xcode/Android Studio required initially. Move to bare CLI or Expo prebuild when you need custom native modules.
Do I need a Mac for Android development?
No. Android builds work on Windows and Linux. macOS is only required for iOS Simulator and App Store builds.
Why is my Text not showing?
All text must be inside a <Text> component. Raw strings as children of <View> will not render.
How do I call localhost API from Android emulator?
Use http://10.0.2.2:PORT instead of localhost. iOS Simulator can use localhost directly.