Site Logo
Find Your Local Branch

Software Development

Learn | React Native: Cross-Platform Mobile Development

Introduction to React Native

React Native is an open-source framework created for building mobile applications using JavaScript and the React programming model. Instead of writing separate native apps in Swift for iOS and Kotlin or Java for Android, developers can write much of the app in a shared codebase and deploy it to both platforms. It exists to reduce duplication, speed up development, and make mobile engineering more accessible to web developers who already know React. In real life, companies use React Native for business apps, e-commerce apps, social platforms, internal enterprise tools, and startup products that need fast development across multiple devices.

At its core, React Native uses components such as View, Text, Image, and ScrollView to build app screens. Unlike React for the browser, React Native does not use HTML elements like div or span. Instead, it maps React components to native UI building blocks. This means you still think in terms of reusable components, props, and state, but the output is a true mobile interface. React Native projects are commonly created with Expo for easier setup or with the React Native CLI for more direct native control. Expo is beginner-friendly, while the CLI is often used when deeper native integration is needed.

Step-by-Step Explanation

A simple React Native app starts by importing tools from react and react-native. Then you define a component function that returns UI. The UI is written in JSX, which looks similar to HTML but uses React Native components. Styling is added with JavaScript objects or StyleSheet.create(). To show data that changes, you use state with hooks such as useState.

The usual beginner flow is: install Node.js, create a project with Expo, start the development server, open the app in an emulator or on a phone, and edit the main file such as App.js or App.tsx. When you save changes, the app reloads quickly, making experimentation easy. This workflow is one reason React Native is widely adopted for rapid product iteration.

Comprehensive Code Examples

Basic example
import React from 'react';
import { View, Text } from 'react-native';

export default function App() {
return (

Hello, React Native!

);
}
Real-world example
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export default function App() {
return (

Welcome Back
Track tasks across iOS and Android.

);
}

const styles = StyleSheet.create({
container: { padding: 24, marginTop: 60 },
title: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 }
});
Advanced usage
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';

export default function App() {
const [count, setCount] = useState(0);

return (

Button pressed: {count} times

Common Mistakes

  • Using HTML tags: Beginners often write div and p. Use View and Text instead.
  • Forgetting to wrap text in Text: Raw strings cannot be placed directly inside a View like on the web.
  • Using CSS syntax directly: React Native styles are JavaScript objects, so properties use camelCase such as backgroundColor.

Best Practices

  • Start with Expo if you are new to mobile development.
  • Build small reusable components instead of one large screen file.
  • Test on both Android and iOS because layouts can differ slightly.
  • Use clear file organization for screens, components, assets, and utilities.

Practice Exercises

  • Create a basic app that shows your name and role using View and Text.
  • Add styles to make a heading larger, bold, and spaced from the top of the screen.
  • Build a counter app with one button that increases a number each time it is pressed.

Mini Project / Task

Build a simple welcome screen for a mobile productivity app that displays an app title, a short description, and a button that counts how many times the user taps it.

Challenge (Optional)

Create a cross-platform introduction screen that shows different welcome text depending on whether the app is running on Android or iOS.

How React Native Works


React Native is a powerful open-source framework that allows developers to build native mobile applications using JavaScript and React. It was created by Facebook (now Meta) and enables a single codebase to target both iOS and Android platforms, significantly reducing development time and cost compared to building separate native apps. At its core, React Native doesn't render web views inside a mobile app. Instead, it compiles JavaScript code into native UI components. This means that an app built with React Native is not a mobile website wrapped in a native container; it is a true native application with native performance and feel. This approach leverages the best of both worlds: the speed and familiarity of web development (React, JavaScript) and the performance and user experience of native mobile apps.

The primary reason for React Native's existence is to solve the problem of platform-specific development. Before React Native, building an app for both iOS and Android required two separate teams, two different codebases (Swift/Objective-C for iOS, Java/Kotlin for Android), and often, two different sets of skills. React Native streamlines this process, allowing web developers to transition into mobile development more easily and enabling companies to maintain a single team for multi-platform deployment. It's widely used in real-world applications by companies like Facebook, Instagram, Airbnb (though they later scaled back), Microsoft, Tesla, and many more, proving its capability for large-scale, production-ready applications.

Core Concepts: The Bridge and Native Modules

The magic behind React Native's cross-platform capabilities lies in its architecture, specifically the 'Bridge' and 'Native Modules'.

  • The JavaScript Thread: This is where your React Native application's JavaScript code runs. It handles the business logic, state management, and component rendering instructions. When you write components in JSX, the JavaScript thread processes them and determines what native UI elements need to be rendered.

  • The Native (UI) Thread: This thread is responsible for rendering the actual native UI components (e.g., `UIView` on iOS, `android.view.View` on Android) and handling user interactions like touches and gestures. It's the same thread that native applications use.

  • The Bridge: This is the communication layer between the JavaScript thread and the Native UI thread. It's a highly optimized asynchronous, serializable, and batched communication mechanism. When your JavaScript code wants to update the UI or access a native feature (like the camera or GPS), it sends messages across the Bridge to the native side. Conversely, when a native event occurs (e.g., a button press), the native side sends a message back across the Bridge to the JavaScript side. This communication is asynchronous to ensure the UI remains responsive and doesn't freeze while JavaScript computations are happening.

  • Native Modules: While React Native provides many pre-built components and APIs, sometimes you need to access platform-specific features not covered by the core library or optimize performance-critical tasks. This is where Native Modules come in. A Native Module is a piece of native code (Objective-C/Swift for iOS, Java/Kotlin for Android) that can be invoked from your JavaScript code. You can write your own Native Modules or use community-contributed ones. For example, if you need to integrate a very specific payment gateway SDK that only has native bindings, you would wrap it in a Native Module.

  • Shadow Tree (Layout Thread): Before the UI components are rendered, React Native constructs a 'Shadow Tree' using a layout engine called Yoga (a C++ implementation of Flexbox). This tree calculates the layout for all components in JavaScript and then sends these layout instructions (sizes, positions) to the Native UI thread. This process ensures consistent layout behavior across platforms while leveraging native rendering.

Step-by-Step Explanation


Let's break down the typical lifecycle and interaction:

1. JavaScript Code Execution: Your React Native application starts by executing its JavaScript bundle. This includes your components, logic, and component declarations.
2. Component Tree Construction: React's rendering process creates a virtual DOM-like representation of your component hierarchy in JavaScript.
3. Layout Calculation (Yoga): The layout engine (Yoga) takes this component tree and calculates the exact position and size of each element based on Flexbox rules. This happens on a separate thread (often called the layout or shadow thread) to avoid blocking the UI.
4. Bridge Communication: The calculated layout and component properties are then serialized into JSON messages and sent across the asynchronous Bridge to the Native UI thread.
5. Native UI Rendering: The Native UI thread receives these messages and translates them into actual native UI components (e.g., `TextView` for ``, `ImageView` for ``). These native components are then rendered on the screen.
6. User Interaction: When a user taps a button or performs a gesture, the native UI thread detects this event. It then serializes the event information and sends it back across the Bridge to the JavaScript thread.
7. JavaScript Event Handling: Your JavaScript code receives the event, updates the application's state, and potentially triggers a re-render cycle (back to step 2).

This seamless, asynchronous communication ensures that even demanding JavaScript operations don't block the UI, providing a smooth, native-like user experience.

Comprehensive Code Examples


Here are some examples illustrating how React Native components map to native elements and how native modules are invoked.

Basic Example: A Simple "Hello World" App

This shows how a basic React Native component renders native text.
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const App = () => {
return (

Hello, React Native!
This text is rendered using native UI components.

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 10,
},
});

export default App;

Explanation: The `` component in React Native isn't just a `div` with text. On iOS, it translates to a `UITextView` or `UILabel`. On Android, it becomes an `android.widget.TextView`. The `StyleSheet.create` method optimizes styles by sending them across the bridge once, caching them natively.

Real-world Example: Accessing Native Device Features (Geolocation)

This demonstrates using a built-in Native Module (Geolocation).
import React, { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet, Alert, Platform } from 'react-native';
import Geolocation from '@react-native-community/geolocation'; // A popular community native module

const LocationTracker = () => {
const [location, setLocation] = useState(null);
const [error, setError] = useState(null);

const requestLocation = () => {
Geolocation.getCurrentPosition(
(position) => {
setLocation(position.coords);
setError(null);
},
(err) => {
setError(err.message);
Alert.alert('Location Error', err.message);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
);
};

useEffect(() => {
// On iOS, you might need to add NSLocationWhenInUseUsageDescription to Info.plist
// On Android, request ACCESS_FINE_LOCATION permission in AndroidManifest.xml
// and handle runtime permissions.
}, []);

return (

Your Current Location

Explanation: The `Geolocation` API is a prime example of a Native Module. When `Geolocation.getCurrentPosition()` is called, the JavaScript thread sends a message across the Bridge. The native side (iOS's Core Location, Android's LocationManager) then handles the request, gets the location, and sends the data back across the Bridge to the JavaScript thread, which then updates the UI.

Advanced Usage: Creating a Custom Native Module (Conceptual)

While writing a full native module requires platform-specific code, here's a conceptual outline of how you'd interact with one in JavaScript.
// JavaScript side (App.js)
import { NativeModules, Platform } from 'react-native';

const { CalendarManager } = NativeModules;

const createCalendarEvent = async () => {
try {
if (Platform.OS === 'ios') {
const eventId = await CalendarManager.addEvent('Party', 'My House', 1678886400); // Unix timestamp
console.log(`Event added with ID: ${eventId}`);
} else if (Platform.OS === 'android') {
CalendarManager.showToast('Event added successfully!');
}
} catch (e) {
console.error(e);
}
};

// ... render a button that calls createCalendarEvent ...

Explanation: Here, `NativeModules.CalendarManager` represents a custom native module you would have written in Objective-C/Swift for iOS and Java/Kotlin for Android. When `CalendarManager.addEvent` is called, the JavaScript thread invokes the corresponding native method via the Bridge. The native method performs the platform-specific action (e.g., adding an event to the iOS Calendar) and potentially returns a result to JavaScript. This demonstrates how React Native extends its capabilities by allowing direct access to native APIs.

Common Mistakes


  • Blocking the JavaScript Thread: Performing heavy computations synchronously on the JavaScript thread can lead to UI unresponsiveness, frame drops, and a janky user experience. Since the JavaScript thread also sends UI update instructions, blocking it stops updates.
    Fix: Use `requestAnimationFrame` for animations, Web Workers (like `react-native-threads`) for heavy background tasks, or offload complex computations to Native Modules.

  • Over-Reliance on Bridge Communication: While necessary, excessive or large data transfers across the Bridge can introduce performance bottlenecks due to serialization/deserialization overhead.
    Fix: Optimize data structures, batch updates where possible, and avoid passing very large arrays or objects frequently. Consider direct native UI components for highly animated or interactive elements if performance is critical.

  • Ignoring Platform Differences: While React Native aims for cross-platform, ignoring subtle (or not-so-subtle) differences between iOS and Android can lead to inconsistent UIs or bugs.
    Fix: Use `Platform.OS` or `Platform.select` to apply platform-specific styles or logic when necessary. Test thoroughly on both platforms.

Best Practices


  • Use `StyleSheet.create` for Styles: This allows React Native to optimize style declarations by sending them across the Bridge only once and referencing them by ID, rather than sending full style objects with every render.

  • Leverage Native Components: Always prefer React Native's built-in components (``, ``, ``, etc.) over trying to recreate complex UI elements with many nested basic components, as they map directly to highly optimized native views.

  • Optimize List Rendering: For long lists, use `FlatList` or `SectionList`. These components are highly optimized for performance, rendering only items currently visible on screen and recycling views.

  • Asynchronous Operations: Always handle network requests, file I/O, and other potentially long-running tasks asynchronously using `async/await` or Promises to keep the UI responsive.

  • Profile Performance: Regularly use React Native's built-in performance tools (e.g., "Enable JS Dev Mode", "Show Perf Monitor") and native profilers (Xcode Instruments, Android Studio Profiler) to identify bottlenecks.

  • Understand the Bridge: Having a clear mental model of how the Bridge works will help you debug performance issues and understand why certain operations might be slower than expected.

Practice Exercises


1. Basic UI Mapping: Create a simple React Native component that displays your name and a small image. Explain which native UI components are likely being rendered on iOS and Android for `` and ``.
2. Button Interaction: Build an app with a button. When the button is pressed, update a `` component to show a counter that increments. Describe the flow of communication across the Bridge when the button is pressed.
3. Platform-Specific Styling: Create a `` component. Make its background color 'lightblue' on iOS and 'lightgreen' on Android using `Platform.select`.

Mini Project / Task


Build a small application that fetches a list of items (e.g., names of fruits) from a dummy API (you can use `https://jsonplaceholder.typicode.com/users` for simplicity, just extract names). Display these names in a `FlatList`. When a user taps on a name, display an `Alert` with the selected name. Focus on ensuring smooth scrolling and responsive interactions.

Challenge (Optional)


Research and implement a basic custom Native Module for Android (or iOS if you have an Apple device/emulator). For example, create a module that exposes a simple method like `greet(name: string)` which shows a native Toast message (Android) or an `Alert` (iOS) with a personalized greeting. This will require diving into platform-specific native code and understanding how to expose methods to JavaScript.

Setting Up the Environment

Setting up the React Native environment means preparing your computer so you can create, run, test, and debug mobile apps for Android and iOS. This step exists because React Native depends on several tools working together: Node.js for JavaScript execution, a package manager for installing libraries, a mobile runtime such as Expo or the React Native CLI, and platform-specific tools like Android Studio and Xcode. In real-world teams, a reliable environment saves hours of troubleshooting and ensures all developers can build the same app consistently. For beginners, the main setup paths are Expo and React Native CLI. Expo is easier and faster to start with because it hides much of the native configuration. React Native CLI gives more control and is commonly used when projects require custom native modules. A typical setup also includes a code editor such as VS Code, an Android emulator or physical device, and optional tools like Watchman on macOS for faster file watching. Understanding these setup choices early helps you avoid mismatched tools, broken builds, and dependency issues.

To begin, install Node.js LTS, because React Native projects use JavaScript tooling powered by Node. Next, install a package manager such as npm or Yarn. Then choose your workflow. If you want the simplest path, install Expo CLI support through the modern command npx create-expo-app. If you want full native access, install the React Native CLI prerequisites, including Android Studio for Android development and Xcode for iOS development on macOS. Android Studio provides the Android SDK, emulator, and build tools. On macOS, Xcode supplies the iOS simulator and compiler required for iPhone apps. After installation, verify your tools from the terminal so you can catch missing dependencies early.

Step-by-Step Explanation

1. Install Node.js LTS from the official Node.js site.
2. Confirm installation with node -v and npm -v.
3. Install VS Code or another editor.
4. For the easy path, create an Expo app with npx create-expo-app MyApp.
5. Move into the project folder using cd MyApp.
6. Start the development server with npm start or npx expo start.
7. Install Android Studio, then create and start an emulator from Device Manager.
8. On macOS, install Xcode and open the iOS Simulator if needed.
9. For physical device testing, install Expo Go on your phone and scan the QR code.
10. If using React Native CLI, ensure Android SDK paths and environment variables are correctly configured.

Comprehensive Code Examples

Basic example: create and run an Expo project

npx create-expo-app MyFirstRNApp
cd MyFirstRNApp
npm start

Real-world example: verify required tools

node -v
npm -v
npx expo --version

Advanced usage: create a React Native CLI project

npx react-native@latest init NativeStarter
cd NativeStarter
npx react-native run-android

Common Mistakes

  • Installing unsupported Node versions: Use the current LTS release for better package compatibility.
  • Skipping Android Studio SDK setup: Install the SDK, emulator, and platform tools completely before running Android builds.
  • Confusing Expo with React Native CLI: Choose one workflow first, then follow its instructions consistently.
  • Forgetting to start an emulator or connect a device: A project may compile correctly but still fail to launch anywhere.

Best Practices

  • Use Expo first if you are new to React Native.
  • Keep Node.js, npm, and platform tools updated, but avoid unstable versions in production work.
  • Test on both an emulator and a physical device when possible.
  • Document your setup steps so future projects are faster to configure.
  • Use version control immediately after project creation.

Practice Exercises

  • Install Node.js and verify it from the terminal using version commands.
  • Create a new Expo app and start the development server successfully.
  • Install Android Studio and launch an Android emulator for testing.

Mini Project / Task

Create a fresh React Native development environment on your computer, build one Expo starter app, and run it successfully on either an Android emulator or a physical phone using Expo Go.

Challenge (Optional)

Set up both workflows on the same machine: one Expo project and one React Native CLI project, then compare which tools, folders, and run commands are different.

Expo vs React Native CLI

Expo and React Native CLI are two common ways to start building React Native apps. Both let you create cross-platform mobile applications, but they solve different problems. Expo exists to make development faster and easier, especially for beginners and teams that want a smooth setup, built-in tooling, and simplified deployment. React Native CLI gives developers more direct access to native Android and iOS projects, which is useful when you need custom native modules, deeper platform control, or highly specific build configurations. In real projects, Expo is often used for prototypes, startup apps, MVPs, internal tools, and many production apps that fit within the Expo ecosystem. React Native CLI is often chosen for apps that require custom native SDKs, advanced integrations, or full control over native code.

The main difference is workflow. Expo provides a managed workflow, where much of the native setup is abstracted away. You can run apps quickly with Expo Go and use features like OTA updates and EAS services. React Native CLI follows a bare workflow, where you work directly with Android Studio, Xcode, Gradle, CocoaPods, and native project files. Expo is easier to start with, while CLI offers maximum flexibility. Modern Expo also supports prebuild and custom development clients, which means the gap is smaller than before, but the decision still depends on project needs.

Step-by-Step Explanation

Expo workflow: install Node.js, create a project with npx create-expo-app, start the dev server with npx expo start, then run the app in Expo Go, an emulator, or a simulator. You mainly work in JavaScript or TypeScript and let Expo manage much of the native complexity.

React Native CLI workflow: install Node.js, Java, Android Studio, and for iOS also Xcode on macOS. Create a project with npx react-native init or current community tooling, then run npx react-native run-android or run-ios. You will see native folders like android/ and ios/, which you can edit directly.

Choose Expo when you want fast onboarding, easier builds, and common mobile features without deep native work. Choose React Native CLI when your app depends on unsupported native libraries, heavy platform customization, or custom native code from the beginning.

Comprehensive Code Examples

import { Text, View } from 'react-native';

export default function App() {
return (

Hello from an Expo app

);
}
import { Button, Linking, View } from 'react-native';

export default function App() {
return (

import { Platform, Text, View } from 'react-native';

export default function App() {
const message = Platform.OS === 'ios' ? 'Running on iOS' : 'Running on Android';

return (

{message}
Use Expo for rapid setup, or CLI for native-level control.

);
}

Common Mistakes

  • Choosing Expo without checking native library support. Fix: verify required packages are supported before committing to the workflow.

  • Choosing CLI too early for a simple app. Fix: start with Expo if speed and ease matter more than native customization.

  • Assuming Expo means no native code is ever possible. Fix: learn about development builds and prebuild for advanced Expo use cases.

Best Practices

  • List app requirements first: camera, push notifications, payments, maps, custom SDKs, offline storage, and analytics.

  • Prefer Expo for learning, prototyping, and many standard production apps.

  • Use React Native CLI when business requirements clearly demand native customization.

  • Document why your team selected one workflow so future developers understand the tradeoffs.

Practice Exercises

  • Write a short comparison list of three reasons to choose Expo and three reasons to choose React Native CLI.

  • Create a simple app screen that displays which workflow you would choose for a notes app and why.

  • Research one native feature, such as Bluetooth or a custom payment SDK, and decide whether Expo or CLI is the better fit.

Mini Project / Task

Build a decision helper screen that shows app requirements and displays whether Expo or React Native CLI is the recommended choice based on those needs.

Challenge (Optional)

Design a migration plan for a small Expo app that now needs a custom native SDK, and describe what tools and steps would change in the development workflow.

Creating Your First App

Creating your first React Native app is the moment where theory becomes something visible and interactive on a phone screen. React Native exists to help developers build mobile apps for iOS and Android using JavaScript and React concepts instead of writing two completely separate native codebases. It is used in real products for dashboards, social apps, e-commerce tools, booking platforms, and internal business apps. In this section, you will build a simple app, understand the project structure, and learn how the main building blocks fit together.

A first app usually starts with a root component named App. In React Native, you do not use regular HTML tags like div or h1; instead, you use mobile-ready components such as View, Text, Image, TextInput, and Button. A View acts like a container, while Text displays readable content. Styling is done with JavaScript objects, often through StyleSheet.create(). Your app updates the UI when state changes, which makes React Native ideal for interactive mobile experiences.

When you create a project with Expo or the React Native CLI, the app entry file loads the main component and renders it on the screen. For beginners, Expo is often easier because it reduces setup complexity and lets you preview your app quickly. The typical process is: create the project, start the development server, open the simulator or Expo Go app, then edit App.js or App.tsx and see live updates.

Step-by-Step Explanation

Start by importing React Native components. Then define a function component called App. Return JSX that describes what should appear on the mobile screen. Wrap related content inside a View, add text with Text, and apply styles using the style prop. Finally, export the component so React Native can render it.

If you want interactivity, add state with useState. State lets your app remember values such as a name, counter, or input field content. Whenever state changes, the component re-renders automatically.

Comprehensive Code Examples

Basic example
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export default function App() {
return (

Hello, React Native!
This is my first app.

);
}

const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
title: { fontSize: 24, fontWeight: 'bold' }
});
Real-world example
import React, { useState } from 'react';
import { View, Text, TextInput, StyleSheet } from 'react-native';

export default function App() {
const [name, setName] = useState('');

return (

Welcome App
style={styles.input}
placeholder='Enter your name'
value={name}
onChangeText={setName}
/>
Hello, {name || 'Guest'}!

);
}

const styles = StyleSheet.create({
container: { flex: 1, padding: 20, justifyContent: 'center' },
heading: { fontSize: 22, marginBottom: 12 },
input: { borderWidth: 1, padding: 10, marginBottom: 12, borderRadius: 8 }
});
Advanced usage
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

export default function App() {
const [count, setCount] = useState(0);

return (

Count: {count}

Common Mistakes

  • Using HTML tags: Replace div and p with View and Text.
  • Forgetting to import components: Every React Native component must be imported from react-native.
  • Writing plain CSS: Use JavaScript style objects, not stylesheet files with web-only properties.

Best Practices

  • Start with Expo for a smoother beginner workflow.
  • Keep your first app small and focused on one interaction.
  • Use meaningful component and style names for readability.
  • Test frequently on both emulator and real device when possible.

Practice Exercises

  • Create a screen that shows your name and a short message using View and Text.
  • Build an input field that updates a greeting as the user types.
  • Create a counter app with buttons to increase and reset the number.

Mini Project / Task

Build a simple personal profile app that shows your name, role, and a short bio, plus a button that changes a welcome message when pressed.

Challenge (Optional)

Expand your first app into a tiny dashboard with two interactive features, such as a greeting input and a counter on the same screen, while keeping the layout clean and readable.

Project Structure

Project structure in React Native is the way files and folders are arranged so developers can build, scale, and maintain mobile applications efficiently.
It exists because React Native apps quickly grow beyond a single screen or component. Real-world apps often include navigation, reusable UI components, API calls, state management, assets, tests, and platform-specific files. Without a clear structure, code becomes difficult to read, debug, and extend.
In practice, good project structure is used in startup apps, enterprise products, e-commerce apps, delivery apps, banking apps, and any mobile product where teams need predictable organization. A React Native project usually contains root configuration files, platform folders such as android and ios, and application code inside folders like src, components, screens, navigation, and assets.
There is no single mandatory structure, but most teams follow a layered approach. Common folder groups include app entry files, reusable components, screen-level pages, hooks, services, utilities, constants, and state. Smaller apps may use a simple flat structure, while medium and large apps often use feature-based or domain-based organization where each feature keeps related screens, styles, and logic together.

Step-by-Step Explanation

Start from the root of a React Native project. Files such as package.json, babel.config.js, and metro.config.js control dependencies and build behavior. The android and ios folders store native platform code.
A common beginner-friendly structure places app code inside a src folder. Inside src, create components for reusable UI pieces, screens for full pages, navigation for route setup, assets for images and fonts, services for API requests, hooks for custom React hooks, utils for helper functions, and constants for fixed values such as colors and endpoints.
Use App.js or index.js as the app entry point. It usually loads providers, themes, and navigation. Screens should stay focused on page layout and user interaction. Repeated UI elements such as buttons, cards, or headers should move into components. Business logic like fetching products or authenticating users should live in services or dedicated state files.
As apps grow, consider a feature-based pattern. For example, an auth feature may contain its own screens, components, hooks, and API methods. This reduces scattered code and makes each feature easier to maintain.

Comprehensive Code Examples

Basic example:

my-app/
App.js
src/
components/
Header.js
screens/
HomeScreen.js
// src/components/Header.js
import React from 'react';
import { View, Text } from 'react-native';

export default function Header() {
return (

My App

);
}
// src/screens/HomeScreen.js
import React from 'react';
import { View } from 'react-native';
import Header from '../components/Header';

export default function HomeScreen() {
return (



);
}

Real-world example:

src/
assets/
components/
ProductCard.js
navigation/
AppNavigator.js
screens/
HomeScreen.js
DetailsScreen.js
services/
api.js
constants/
colors.js
// src/services/api.js
export async function fetchProducts() {
const response = await fetch('https://example.com/products');
return response.json();
}

Advanced usage:

src/
features/
auth/
screens/LoginScreen.js
components/LoginForm.js
services/authApi.js
profile/
screens/ProfileScreen.js
hooks/useProfile.js
shared/
components/Button.js
utils/formatDate.js

This advanced structure keeps feature code together while still sharing common building blocks in a shared folder.

Common Mistakes

  • Putting everything in one folder: Fix by separating screens, components, services, and assets.
  • Mixing reusable and screen-specific code: Fix by moving shared UI into components and page-only logic into screens.
  • No src folder in larger apps: Fix by grouping all app code under src for cleaner root organization.
  • Scattered feature files: Fix by using feature-based folders when the app grows.

Best Practices

  • Keep names descriptive and consistent, such as HomeScreen, UserCard, and authApi.
  • Store reusable constants like colors, spacing, and endpoints in dedicated folders.
  • Avoid deeply nested folders unless the app genuinely needs them.
  • Choose one structure style early and use it consistently across the project.
  • Refactor structure as the app grows instead of waiting for chaos.

Practice Exercises

  • Create a src folder and organize an app into components, screens, and assets.
  • Move a repeated header and button from a screen into reusable component files.
  • Design a folder structure for a shopping app with screens, navigation, services, and constants.

Mini Project / Task

Organize a small React Native app for a notes application with folders for screens, reusable components, local assets, and a service file that simulates loading note data.

Challenge (Optional)

Redesign a simple screen-based structure into a feature-based structure for an app that includes authentication, user profile, and product listing modules.

Core Components Overview

React Native provides a set of built-in components that act as the foundation of every mobile interface. These components exist so developers can write app screens using a React-style syntax while still producing native-looking results on Android and iOS. In real projects, core components are used to build login pages, product cards, profile screens, settings pages, chat layouts, forms, and almost every visual part of an app. Instead of using regular HTML elements like div, span, or img, React Native uses mobile-focused components such as View, Text, Image, ScrollView, TextInput, Button, and Pressable.

The most important idea is that each component has a specific purpose. View is the main container for layout, similar to a flexible box that groups other elements. Text displays readable content and must wrap most visible text. Image renders local or remote pictures. ScrollView allows content to move vertically or horizontally when it does not fit on screen. TextInput collects user input, while Button and Pressable trigger actions such as submitting a form or opening a screen. Learning when to use each one is essential because React Native apps are built by combining these pieces into reusable UI sections.

Step-by-Step Explanation

Start by importing the components you need from react-native. Then place them inside your main component function. A beginner-friendly pattern is to use one outer View as the screen container, add Text for labels and titles, and include interactive components only where needed. Remember that plain text cannot be placed directly inside View; it should be wrapped in Text. Styling is usually added with the style prop and JavaScript objects. Layout is based on Flexbox, so containers can align and space children predictably across devices.

Some common component roles are easy to memorize: use SafeAreaView for content that should avoid notches and status bars, use Image for logos or photos, use ScrollView for smaller scrollable screens, and use Pressable when you want more control over tap behavior than a simple button provides.

Comprehensive Code Examples

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export default function App() {
return (

Welcome to React Native
This screen uses View and Text.

);
}

const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
title: { fontSize: 22, fontWeight: 'bold', marginBottom: 10 }
});
import React from 'react';
import { SafeAreaView, View, Text, Image, Pressable, StyleSheet } from 'react-native';

export default function ProfileCard() {
return (


source={{ uri: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=300&q=80' }}
style={styles.avatar}
/>
Alex Johnson
Mobile Developer

Follow



);
}

const styles = StyleSheet.create({
screen: { flex: 1, justifyContent: 'center', alignItems: 'center' },
card: { padding: 20, backgroundColor: '#fff', borderRadius: 12, alignItems: 'center' },
avatar: { width: 80, height: 80, borderRadius: 40, marginBottom: 12 },
name: { fontSize: 20, fontWeight: '600' },
button: { marginTop: 12, backgroundColor: '#007AFF', paddingHorizontal: 16, paddingVertical: 10, borderRadius: 8 },
buttonText: { color: '#fff' }
});
import React, { useState } from 'react';
import { ScrollView, View, Text, TextInput, Pressable, StyleSheet } from 'react-native';

export default function FeedbackScreen() {
const [message, setMessage] = useState('');

return (

Feedback Form
placeholder='Write your feedback'
value={message}
onChangeText={setMessage}
style={styles.input}
multiline
/>

Send Feedback


Preview:
{message || 'Nothing typed yet'}


);
}

const styles = StyleSheet.create({
container: { padding: 20 },
heading: { fontSize: 24, fontWeight: 'bold', marginBottom: 16 },
input: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 12, minHeight: 100 },
submit: { backgroundColor: 'black', padding: 14, borderRadius: 8, marginTop: 12 },
submitText: { color: 'white', textAlign: 'center' },
previewBox: { marginTop: 20 }
});

Common Mistakes

  • Placing raw text inside a View: Wrap display text in a Text component.
  • Using HTML tags like div or img: Replace them with React Native components such as View and Image.
  • Forgetting scroll support: Use ScrollView when content may exceed screen height.
  • Using Button for every interaction: Prefer Pressable when custom styling or touch feedback is needed.

Best Practices

  • Choose components based on purpose, not habit.
  • Keep screen layouts organized with a clear parent View.
  • Use SafeAreaView for top-level mobile screens when needed.
  • Group repeated UI into reusable custom components.
  • Test layouts on multiple screen sizes.

Practice Exercises

  • Create a screen with a View, two Text elements, and one styled title.
  • Build a profile card using Image, Text, and Pressable.
  • Create a feedback screen with ScrollView and TextInput.

Mini Project / Task

Build a simple mobile contact card screen that shows a profile image, name, short bio, and a message button using only core React Native components.

Challenge (Optional)

Create a scrollable product details screen that combines SafeAreaView, ScrollView, Image, Text, and Pressable in a clean mobile layout.

View and Text Components


In React Native, the building blocks of your user interface are components. Among the most fundamental are the View and Text components. Think of View as the most basic container, similar to a

in web development, but with the added power of native styling and layout properties. It's designed to support layout with Flexbox, styling, and touch handling, and it doesn't directly display anything visible itself unless styled with a background color or border. Its primary purpose is to encapsulate and arrange other components. Text, on the other hand, is specifically designed to display text. Unlike web development where text can be placed directly inside a
, in React Native, all text must be wrapped inside a component. This distinction is crucial for proper rendering and styling across different mobile platforms.


These components are ubiquitous in nearly every React Native application. You'll use View to create layout structures like headers, footers, sidebars, and content areas, effectively organizing your UI elements. Text is used for everything from simple labels and paragraph text to complex formatted strings within your app. For instance, a social media app would use View to structure the user's profile, post cards, and navigation bars, and Text to display usernames, post content, timestamps, and comments. Understanding how to effectively use and style these two components is foundational to building any React Native application.


Step-by-Step Explanation


Let's break down the usage of View and Text:


  • Importing Components: Both View and Text must be imported from the 'react-native' library at the top of your component file.

  • View Component:
    - It acts as a container for other components (including other Views and Texts).
    - It supports styling via the style prop, accepting an object or an array of style objects.
    - Common styles include flex properties for layout, backgroundColor, padding, margin, borderWidth, etc.
    - It does not inherently scroll; for scrollable content, you'd use a ScrollView.

  • Text Component:
    - All text content in React Native must be nested inside a component.
    - It also supports styling via the style prop.
    - Text-specific styles include fontSize, color, fontWeight, textAlign, lineHeight, etc.
    - Text components can be nested, and styling is inherited, but child Text styles can override parent Text styles.

Comprehensive Code Examples


Basic example

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const BasicComponents = () => {
return (

Hello, React Native!
This is a basic example of View and Text components.

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f0f0f0',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#333',
marginBottom: 10,
},
paragraph: {
fontSize: 16,
color: '#666',
textAlign: 'center',
},
});

export default BasicComponents;

Real-world example

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const UserProfileCard = ({ username, bio, followers, following }) => {
return (


@{username}


{bio}



{followers}
Followers


{following}
Following



);
};

const styles = StyleSheet.create({
card: {
backgroundColor: 'white',
borderRadius: 10,
padding: 15,
margin: 20,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 5,
width: '90%',
},
header: {
marginBottom: 10,
},
username: {
fontSize: 20,
fontWeight: 'bold',
color: '#333',
},
body: {
marginBottom: 15,
},
bioText: {
fontSize: 14,
color: '#555',
lineHeight: 20,
},
footer: {
flexDirection: 'row',
justifyContent: 'space-around',
borderTopWidth: 1,
borderTopColor: '#eee',
paddingTop: 10,
},
statItem: {
alignItems: 'center',
},
statNumber: {
fontSize: 18,
fontWeight: 'bold',
color: '#007bff',
},
statLabel: {
fontSize: 12,
color: '#777',
},
});

export default () => (
username="react_dev"
bio="Passionate about building mobile apps with React Native. Love clean code and great UX!"
followers={1234}
following={567}
/>
);

Advanced usage (Nested Text with different styles)

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const StyledParagraph = () => {
return (


This is a bold word,
and this is italic.
You can even combine multiple styles.
Important message here!


);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
baseText: {
fontSize: 18,
color: '#444',
lineHeight: 26,
},
boldText: {
fontWeight: 'bold',
color: '#000',
},
italicText: {
fontStyle: 'italic',
color: '#555',
},
underlineText: {
textDecorationLine: 'underline',
},
redText: {
color: 'red',
fontWeight: 'bold',
},
});

export default StyledParagraph;

Common Mistakes


  • Putting raw text directly into a View: React Native requires all text to be wrapped inside a component. If you try to place text directly into a View, you'll get an error like "Text strings must be rendered within a component."
    Fix: Always wrap your text: My text.

  • Applying text styles to a View: Styles like fontSize, fontWeight, or color only apply to Text components. Applying them to a View will have no effect on nested text.
    Fix: Apply text-specific styles to the Text component itself or an ancestor Text component.

  • Forgetting to import View or Text: If you use these components without importing them from 'react-native', you'll get an error stating the component is not defined.
    Fix: Always include import { View, Text, StyleSheet } from 'react-native'; at the top of your file.

Best Practices


  • Use StyleSheet.create: Always define your styles using StyleSheet.create for better performance, organization, and debugging.

  • Semantic Nesting: Use View for layout and grouping, and Text for displaying text. Avoid using View when Text is more appropriate (e.g., don't try to style a View to look like text).

  • Inheritance with Text: Leverage text style inheritance. Define common text styles on an outer Text component, and only define specific overrides on inner Text components.

  • Flexbox for Layout: Master Flexbox properties on View components to create responsive and flexible layouts.

  • Keep Styles Separate: For complex components, consider separating styles into different files or using a styling library for better maintainability.

Practice Exercises


  • Exercise 1 (Basic Layout): Create a component that displays your name and a short bio. Place your name in a larger, bold font and your bio in a regular font below it. Both should be centered horizontally.

  • Exercise 2 (Card Structure): Build a simple 'product card' layout. It should have a View acting as the card container with a light gray background and rounded corners. Inside, place two Text components: one for the product name (bold, larger font) and one for the price (smaller font, green color).

  • Exercise 3 (Nested Text Styling): Display a sentence that includes a specific word or phrase highlighted in a different color and made italic, using nested Text components. For example: "Learn React Native to build amazing apps."

Mini Project / Task


Create a simple 'Welcome Screen' for an app. This screen should have a main View container that fills the entire screen and centers its content. Inside, display a large, bold welcome message (e.g., "Welcome to My App!") and a smaller descriptive paragraph below it (e.g., "Your journey starts here."). Add a subtle background color to the main container and some padding around the text content.


Challenge (Optional)


Expand the 'Welcome Screen' from the mini-project. Add a third line of text at the very bottom of the screen (e.g., "Version 1.0") that is aligned to the bottom-center. Achieve this using Flexbox properties on your main View container and potentially an additional nested View for the bottom text.

Image and ImageBackground



The and components are fundamental building blocks in React Native for displaying images within your mobile applications. They serve distinct purposes but both are crucial for creating visually rich user interfaces. The component is used to display various types of images, including network images, local static resources, and even base64 encoded images. It's the go-to component for showing user avatars, product photos, icons, and any other visual content that doesn't need to serve as a background for other UI elements. Its primary purpose is to render an image efficiently, handling aspects like resizing, caching, and loading states. Why does it exist? Because unlike web development where an tag suffices, mobile development often requires tighter control over image loading, performance, and platform-specific optimizations. React Native's component abstracts these complexities, offering a unified API across iOS and Android. In real life, you'll see used extensively in social media apps for profile pictures, e-commerce applications for product listings, news apps for article thumbnails, and virtually every app that has visual content.

The component, on the other hand, is a specialized version of . Its core function is to display an image that serves as the background for other components. Think of it as a with an image behind it. This is incredibly useful for creating visually appealing headers, splash screens, card layouts, or any section of your UI where text or other elements need to overlay an image. While you could technically achieve a similar effect by absolutely positioning an behind other components, simplifies this pattern, making the code cleaner and more readable. It ensures that the child components are rendered on top of the image, handling layout and z-index automatically. You'll find in login screens with scenic backgrounds, hero sections of landing pages, or custom card designs in dashboards.

Step-by-Step Explanation


Both and components share several common props, with source and style being the most crucial. The source prop defines where the image comes from. It can take an object with a uri key for network images (e.g., { uri: 'https://example.com/image.jpg' }) or a require() call for local static assets (e.g., require('./assets/my-image.png')). The style prop is used to define the dimensions (width, height) and other styling properties like borderRadius or resizeMode. resizeMode controls how the image should be resized to fit its container. Common values include 'cover' (scales the image to cover the entire container, possibly cropping parts), 'contain' (scales to fit within the container, preserving aspect ratio, possibly leaving empty space), 'stretch' (stretches to fill the container, ignoring aspect ratio), 'repeat' (repeats the image), and 'center' (centers the image). For , remember that its children are rendered on top, so you typically style the itself with width and height and then position its children using standard Flexbox or absolute positioning.

Comprehensive Code Examples


Basic Example: Displaying a Local Image

import React from 'react';
import { View, Image, StyleSheet } from 'react-native';

const App = () => {
return (

source={require('./assets/logo.png')} // Make sure you have a logo.png in your assets folder
style={styles.localImage}
/>

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
localImage: {
width: 150,
height: 150,
borderRadius: 75,
},
});

export default App;


Real-world Example: User Profile Card with ImageBackground

import React from 'react';
import { View, Text, ImageBackground, StyleSheet } from 'react-native';

const ProfileCard = () => {
return (
source={{ uri: 'https://images.unsplash.com/photo-1502602898669-a393c52e4a42?auto=format&fit=crop&w=1000&q=80' }}
style={styles.backgroundImage}
imageStyle={styles.imageBackgroundStyle} // Style for the image itself, not the container
>

Jane Doe
Mobile Developer | React Native Enthusiast


);
};

const styles = StyleSheet.create({
backgroundImage: {
width: '90%',
height: 200,
marginVertical: 20,
alignSelf: 'center',
justifyContent: 'flex-end',
borderRadius: 10,
overflow: 'hidden', // Essential for borderRadius to work with ImageBackground
},
imageBackgroundStyle: {
resizeMode: 'cover',
},
overlay: {
backgroundColor: 'rgba(0,0,0,0.4)',
padding: 15,
width: '100%',
},
profileName: {
color: 'white',
fontSize: 22,
fontWeight: 'bold',
},
profileBio: {
color: 'white',
fontSize: 14,
marginTop: 5,
},
});

export default ProfileCard;


Advanced Usage: Handling Image Loading State and Error

import React, { useState } from 'react';
import { View, Image, ActivityIndicator, Text, StyleSheet } from 'react-native';

const AdvancedImage = () => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);

const imageUrl = 'https://reactnative.dev/img/tiny_logo.png'; // A valid URL
const invalidImageUrl = 'https://invalid.url/image.png'; // An invalid URL

return (

Valid Image:

{loading && !error && }
{error && Failed to load image.}
source={{ uri: imageUrl }}
style={styles.advancedImage}
onLoadStart={() => {
setLoading(true);
setError(false);
}}
onLoadEnd={() => setLoading(false)}
onError={() => {
setError(true);
setLoading(false);
}}
resizeMode="contain"
/>


Invalid Image (will show error):

source={{ uri: invalidImageUrl }}
style={styles.advancedImage}
onError={() => console.log('Invalid image URL failed to load')}
defaultSource={require('./assets/placeholder.png')} // Fallback image for errors or while loading
// Make sure you have a placeholder.png in your assets folder
/>


);
};

const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
paddingTop: 50,
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginTop: 20,
marginBottom: 10,
},
imageWrapper: {
width: 200,
height: 200,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f0f0f0',
borderRadius: 10,
overflow: 'hidden',
},
advancedImage: {
width: '100%',
height: '100%',
},
activityIndicator: {
position: 'absolute',
zIndex: 1,
},
errorText: {
position: 'absolute',
zIndex: 1,
color: 'red',
textAlign: 'center',
padding: 10,
},
});

export default AdvancedImage;


Common Mistakes



  • Forgetting Dimensions: Unlike web tags, React Native's and components require explicit width and height styles, or they won't render. If you don't specify them, the image will have zero dimensions and won't be visible.
    Fix: Always provide width and height in your style prop, or use Flexbox properties like flex: 1 to make them fill their parent.

  • Incorrect Local Image Path: Using a string path instead of require() for local images, or providing an incorrect path.
    Fix: For local images, always use source={require('./path/to/image.png')}. Ensure the path is correct relative to the component file.

  • Not handling overflow: 'hidden' for borderRadius on ImageBackground: Applying borderRadius directly to might not clip the image itself, especially on Android.
    Fix: Add overflow: 'hidden' to the ImageBackground style, or apply borderRadius to the imageStyle prop if you want to style the underlying image directly.

  • Network Image Caching Issues: When updating a network image at the same URL, the old image might be cached and displayed.
    Fix: Append a query parameter (e.g., a timestamp) to the uri to force a refresh: uri: 'https://example.com/image.jpg?' + new Date().getTime().



Best Practices



  • Optimize Image Sizes: Always use images that are appropriately sized for mobile devices. Large images can significantly impact performance and memory usage. Consider using image optimization tools or serving different image sizes for different screen densities.

  • Use resizeMode Effectively: Choose the correct resizeMode ('cover', 'contain', 'stretch') to control how images fit within their containers. 'cover' is often good for backgrounds, while 'contain' is better for logos or icons where the full image must be visible.

  • Provide defaultSource or Fallback: For network images, use the defaultSource prop to display a placeholder image while the actual image loads, or implement custom loading indicators and error states using onLoadStart, onLoadEnd, and onError callbacks.

  • Preload Images: For images that are critical to the initial user experience (e.g., splash screen images), consider preloading them using Image.prefetch() or Image.queryCache() to ensure they appear instantly.

  • Accessibility: For meaningful images, use the accessibilityLabel prop to describe the image content for screen readers, improving accessibility for users with visual impairments.



Practice Exercises



  • Exercise 1 (Beginner): Create a new React Native component that displays three different images: one from a local asset, one from a network URL, and one with a circular shape using borderRadius. Each image should have a distinct resizeMode (e.g., 'cover', 'contain', 'stretch').

  • Exercise 2 (Intermediate): Build a simple card component that uses as its header. Overlay a component with a title and a subtitle on top of the image. Ensure the text is readable by applying a semi-transparent overlay.

  • Exercise 3 (Advanced): Implement an image gallery screen. Display a grid of components loaded from a list of URLs. For each image, show an while it's loading and a fallback text or icon if it fails to load.



Mini Project / Task


Design and implement a 'Login Screen' layout. This screen should feature a full-width at the top, occupying about 40% of the screen height, displaying a scenic image. Overlay a bold title like 'Welcome Back!' on this . Below the image, add two fields for username and password, and a

TextInput Component

The TextInput component in React Native is used to collect typed input from users. It is one of the most important building blocks in mobile apps because many real applications require entering names, emails, passwords, search terms, comments, phone numbers, and other data. Without TextInput, users could only read content and tap buttons, but they would not be able to actively provide information. In practice, this component appears in login screens, signup forms, profile editors, checkout pages, chat apps, and search bars.

React Native provides several useful variations of TextInput through props rather than separate components. For example, you can create a single-line field for names, a secure password field with secureTextEntry, a numeric field using keyboardType, or a multi-line input with multiline. You can also control the value using React state, which is the most common pattern. In a controlled input, the displayed value comes from state, and every keystroke updates that state using onChangeText.

Step-by-Step Explanation

To use TextInput, first import it from react-native. Then create a state variable with useState to store the current text. Pass that state into the value prop, and update it with onChangeText. Add a placeholder to guide the user. You can style the field with borders, padding, and rounded corners. For special behavior, use props such as secureTextEntry for passwords, keyboardType="email-address" for email, keyboardType="numeric" for numbers, and multiline for larger text areas. This pattern makes the component predictable and easy to validate later.

Comprehensive Code Examples

import React, { useState } from 'react';
import { View, TextInput, StyleSheet } from 'react-native';

export default function App() {
const [name, setName] = useState('');

return (

style={styles.input}
placeholder="Enter your name"
value={name}
onChangeText={setName}
/>

);
}

const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 20 },
input: { borderWidth: 1, borderColor: '#ccc', padding: 12, borderRadius: 8 }
});
import React, { useState } from 'react';
import { View, TextInput, StyleSheet } from 'react-native';

export default function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

return (

style={styles.input}
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
value={email}
onChangeText={setEmail}
/>
style={styles.input}
placeholder="Password"
secureTextEntry
value={password}
onChangeText={setPassword}
/>

);
}

const styles = StyleSheet.create({
container: { padding: 20 },
input: { borderWidth: 1, borderColor: '#bbb', padding: 12, borderRadius: 8, marginBottom: 12 }
});
import React, { useState } from 'react';
import { View, TextInput, Text, StyleSheet } from 'react-native';

export default function BioInput() {
const [bio, setBio] = useState('');
const maxLength = 120;

return (

style={styles.textArea}
placeholder="Write a short bio"
multiline
numberOfLines={4}
maxLength={maxLength}
value={bio}
onChangeText={setBio}
/>
{bio.length}/{maxLength}

);
}

const styles = StyleSheet.create({
container: { padding: 20 },
textArea: { borderWidth: 1, borderColor: '#999', borderRadius: 8, padding: 12, minHeight: 100, textAlignVertical: 'top' }
});

Common Mistakes

  • Forgetting to connect value and onChangeText. Fix: use state so the input is controlled properly.

  • Using the wrong keyboard type for data entry. Fix: set keyboardType for email, phone, or numeric input.

  • Not disabling capitalization for emails or usernames. Fix: use autoCapitalize="none" where needed.

  • Creating multiline fields without enough height. Fix: add styles like minHeight and textAlignVertical: 'top'.

Best Practices

  • Prefer controlled inputs for reliable validation and form handling.

  • Use meaningful placeholders, but do not rely on them as the only label in larger forms.

  • Choose input props that match the data type, such as secureTextEntry and keyboardType.

  • Keep styling consistent across all fields for a professional mobile UI.

Practice Exercises

  • Create a screen with one TextInput for a username and display the typed value below it.

  • Build a password field using secureTextEntry and add custom styling.

  • Create a multiline comment box with a character limit and a live character counter.

Mini Project / Task

Build a simple profile form with three fields: full name, email, and short bio. Use the correct keyboard and multiline settings for each field.

Challenge (Optional)

Create a small signup form that disables submission until all TextInput fields contain valid-looking values.

Button and TouchableOpacity


Welcome to the world of interactive React Native applications! At the heart of any user interface lies the ability for users to interact with it, and in mobile development, this interaction often begins with tapping on elements. In React Native, the primary components for handling taps and creating interactive elements are Button and TouchableOpacity. These components allow developers to create tappable regions that respond to user input, triggering actions within the application. The Button component is a basic, platform-agnostic component that renders a simple button with a title and an onPress handler. It's designed for simple use cases where a standard button look and feel is sufficient. TouchableOpacity, on the other hand, is a more versatile and customizable component. It's not a button itself, but rather a wrapper that makes any of its children tappable. When pressed, it reduces the opacity of its children, providing visual feedback to the user that an interaction has occurred. This makes it ideal for creating custom buttons, icons, or any other interactive element that requires a specific design.

These components are crucial for building intuitive navigation, triggering data submissions, opening modals, or performing any action that requires user initiation. Without them, mobile applications would be static and unresponsive. They exist to bridge the gap between static content and dynamic user experiences, allowing developers to craft engaging and functional interfaces.

The Button component is relatively simple. Its main props are title (the text displayed on the button), onPress (a function called when the button is pressed), color (the text color on iOS and background color on Android), and disabled (a boolean to disable interaction). TouchableOpacity is a more generic wrapper component. Its primary prop is onPress, which functions identically to the Button's onPress. Additionally, it offers props like activeOpacity (to control the opacity when pressed, default is 0.2), onLongPress, delayLongPress, and hitSlop for more advanced touch handling. The key difference lies in their children: Button internally handles its text content via the title prop, while TouchableOpacity can wrap any React Native components (like Text, Image, or View), allowing for complete design freedom.

Step-by-Step Explanation


Button:
1. Import: Begin by importing Button from 'react-native'.
2. Placement: Place the

ScrollView and FlatList

ScrollView and FlatList are two essential React Native components used to display content that may not fit on the screen at once. Mobile apps constantly deal with vertical content: profile pages, settings screens, article bodies, activity feeds, shopping lists, and message previews. A ScrollView is designed for smaller sets of content and renders all child components at once. This makes it simple and flexible, especially for static layouts such as forms, onboarding pages, and settings sections. FlatList is built for larger, dynamic collections of data. Instead of rendering everything immediately, it renders items efficiently as they appear on the screen, which improves performance and memory usage. In real apps, this matters a lot when showing hundreds or thousands of rows.

The key difference is rendering strategy. ScrollView eagerly renders all children, so it is easy to use but can become slow with long lists. FlatList is a virtualized list, meaning it only renders visible items plus a small buffer. FlatList also provides features like item separators, headers, footers, pull-to-refresh support, and stable item keys. Beginners often start with ScrollView because it feels straightforward, but professional apps usually prefer FlatList for anything data-driven or repeatable. A good rule is simple: if you are mapping over many items, reach for FlatList; if you are presenting a small screen of mixed content, ScrollView is often enough.

Step-by-Step Explanation

To use ScrollView, import it from React Native and place child components inside it. The children can be text, images, cards, buttons, or custom components. You can scroll vertically by default or horizontally with the horizontal prop. For FlatList, import it and provide at least two important props: data and renderItem. The data prop is an array, while renderItem is a function that returns the UI for each element. You should also provide a keyExtractor so each row has a stable unique key. FlatList can be vertical or horizontal and supports additional props like ItemSeparatorComponent, ListHeaderComponent, and numColumns.

When deciding between them, ask two questions. First, is the content small and mostly static? Use ScrollView. Second, is the content a repeated list from an array or API? Use FlatList. This decision improves performance and keeps code maintainable.

Comprehensive Code Examples

import React from 'react'; import { ScrollView, Text, View } from 'react-native'; export default function App() { return (  Welcome Profile details Settings  ); }
import React from 'react'; import { FlatList, Text, View } from 'react-native'; const products = [ { id: '1', name: 'Phone' }, { id: '2', name: 'Tablet' }, { id: '3', name: 'Laptop' } ]; export default function App() { return (  item.id} renderItem={({ item }) => (  {item.name}  )} /> ); }
import React from 'react'; import { FlatList, Text, View } from 'react-native'; const messages = Array.from({ length: 50 }, (_, i) => ({ id: String(i + 1), title: `Message ${i + 1}` })); export default function App() { return (  item.id} ListHeaderComponent={Inbox} ItemSeparatorComponent={() => } renderItem={({ item }) => (  {item.title}  )} /> ); }

Common Mistakes

  • Using ScrollView for very large datasets, which causes slow rendering. Fix: use FlatList for long or dynamic lists.
  • Forgetting keyExtractor in FlatList. Fix: provide a unique string key from each item.
  • Nesting many components inefficiently inside lists. Fix: keep row components lightweight and reusable.
  • Using array index as the key when data changes often. Fix: prefer stable IDs from the data source.

Best Practices

  • Use ScrollView for small static screens such as settings and terms pages.
  • Use FlatList for API data, feeds, catalogs, and anything repeatable.
  • Create separate item components to keep renderItem clean.
  • Provide stable unique keys for correct updates and smoother rendering.
  • Test list performance on real devices, not only emulators.

Practice Exercises

  • Create a ScrollView containing 10 text blocks that describe a user profile screen.
  • Build a FlatList that displays 15 course names from an array.
  • Add a separator between FlatList items and a header title above the list.

Mini Project / Task

Build a mobile contacts screen where each contact is rendered with FlatList, showing a name and phone number, plus a header labeled My Contacts.

Challenge (Optional)

Create a product gallery that uses FlatList with two columns and displays item cards containing a title and price.

SectionList

SectionList is a React Native component used to render grouped lists of data, where items are organized into sections with optional headers and footers. It exists because many real mobile screens are not just flat lists. Contacts apps group names by first letter, shopping apps group products by category, and settings screens group options into meaningful blocks. SectionList gives you a structured, performant way to display this kind of content while supporting scrolling, item recycling, separators, sticky headers, and large datasets.

At its core, SectionList expects an array of section objects. Each section usually contains a title and a data array. The data array holds the items for that section. You provide a renderItem function to render each item and a renderSectionHeader function to render section headings. Common related features include keyExtractor for stable keys, ItemSeparatorComponent for spacing between items, SectionSeparatorComponent for separation between sections, and ListHeaderComponent or ListFooterComponent for content above or below the whole list. Sticky headers are especially useful because section headers can remain visible while the user scrolls.

Step-by-Step Explanation

To use SectionList, first import it from React Native. Next, create your sections array. Each object should contain a data property because SectionList reads items from there. Then define renderItem, which receives an object containing item, index, and section. After that, define renderSectionHeader to display the section label. Finally, pass the sections array into the sections prop. For reliable rendering, use keyExtractor so each item gets a unique key. If your list is large, avoid inline heavy logic inside render functions and keep item components simple. You can also enable sticky headers with stickySectionHeadersEnabled.

Comprehensive Code Examples

import React from 'react'; import { SectionList, Text, View, StyleSheet } from 'react-native'; const sections = [ { title: 'Fruits', data: ['Apple', 'Banana', 'Orange'] }, { title: 'Vegetables', data: ['Carrot', 'Potato', 'Spinach'] }, ]; export default function App() { return (  item + index} renderItem={({ item }) => {item}} renderSectionHeader={({ section }) => {section.title}} /> ); } const styles = StyleSheet.create({ header: { fontSize: 18, fontWeight: 'bold', backgroundColor: '#eee', padding: 8 }, item: { fontSize: 16, padding: 10, borderBottomWidth: 1, borderBottomColor: '#ddd' }, });
import React from 'react'; import { SectionList, Text, View } from 'react-native'; const sections = [ { title: 'Today', data: [ { id: '1', task: 'Reply to emails' }, { id: '2', task: 'Team meeting' } ] }, { title: 'Tomorrow', data: [ { id: '3', task: 'Prepare report' } ] }, ]; export default function TaskList() { return (  item.id} renderItem={({ item }) => (  {item.task}  )} renderSectionHeader={({ section }) => (  {section.title}  )} stickySectionHeadersEnabled={true} /> ); }
import React from 'react'; import { SectionList, Text, View } from 'react-native'; const sections = [ { title: 'A', data: [{ id: '1', name: 'Alice', online: true }] }, { title: 'B', data: [{ id: '2', name: 'Bob', online: false }] }, ]; const Row = React.memo(({ item }) => (  {item.name} {item.online ? 'Online' : 'Offline'}  )); export default function ContactsList() { return (  item.id} renderItem={({ item }) => } renderSectionHeader={({ section }) => {section.title}} ItemSeparatorComponent={() => } /> ); }

Common Mistakes

  • Wrong data shape: forgetting the data property in each section. Fix it by using objects like { title: 'A', data: [...] }.
  • Unstable keys: using weak keys can cause rendering bugs. Fix it with unique IDs in keyExtractor.
  • Heavy render functions: putting complex calculations inside renderItem slows scrolling. Fix it by preparing data earlier and using memoized row components.
  • Confusing FlatList and SectionList: FlatList is for one flat array, while SectionList is for grouped arrays. Choose based on your data structure.

Best Practices

  • Keep section data normalized and predictable.
  • Use simple, reusable item components.
  • Prefer unique item IDs over index-based keys.
  • Use sticky headers when grouped navigation matters.
  • Add separators and headers to improve readability.
  • Test with long datasets to confirm smooth performance.

Practice Exercises

  • Create a SectionList that groups animals into Mammals, Birds, and Fish.
  • Build a grocery list grouped by category such as Dairy, Snacks, and Drinks.
  • Add section headers and item separators to an existing SectionList and style them clearly.

Mini Project / Task

Build a mobile contacts screen where names are grouped alphabetically, each section has a sticky header, and every contact row shows a name and phone number.

Challenge (Optional)

Create a SectionList for a chat archive grouped by month, and add a custom section footer that shows how many messages are in each month.

StyleSheet and Styling


Styling in React Native is a fundamental aspect of building visually appealing and user-friendly mobile applications. Unlike web development which primarily uses CSS, React Native employs a JavaScript-based styling approach, drawing inspiration from CSS but with key differences. The primary tool for styling is the StyleSheet API, which provides an optimized way to define and manage styles. It exists to offer a performant and consistent styling experience across different platforms (iOS and Android). By abstracting platform-specific styling nuances, StyleSheet allows developers to write styles once and apply them everywhere, greatly enhancing the cross-platform development experience. In real-life applications, styling is crucial for branding, user experience, and accessibility. Whether it's creating a consistent theme, ensuring readability, or designing interactive elements, effective styling is at the heart of a polished mobile app.


At its core, React Native styling involves passing style objects to components via a style prop. These style objects are similar to CSS but use camelCase for property names (e.g., backgroundColor instead of background-color). The StyleSheet.create() method is the recommended way to define styles. This method takes an object where keys are style names and values are style objects. It performs optimizations like serializing style objects, sending them to the native side only once, and caching them, which improves performance. Another important concept is the ability to combine multiple style objects using an array, where styles later in the array override earlier ones. This allows for flexible and dynamic styling. Furthermore, React Native supports inline styles, although StyleSheet.create() is generally preferred for reusability and performance. Conditional styling, based on component props or state, is also a common pattern, allowing styles to adapt to different scenarios.


Step-by-Step Explanation


To style a React Native component, you typically follow these steps:



  • Import StyleSheet: At the top of your component file, import StyleSheet from 'react-native'.
    import { StyleSheet } from 'react-native';

  • Define Styles: Create a style object using StyleSheet.create(). This object will contain all your named styles.
    const styles = StyleSheet.create({
    container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
    },
    title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: 'blue',
    },
    });

  • Apply Styles: Pass the defined styles to the style prop of your components. You can access individual styles using dot notation (e.g., styles.container).

    Hello React Native!

  • Combine Styles: To apply multiple styles, pass an array of style objects to the style prop. The styles defined later in the array will override earlier ones if there are conflicting properties.
    Red Title

  • Inline Styles: For simple, non-reusable styles, you can pass a plain JavaScript object directly to the style prop.
    Inline Styled Text


Comprehensive Code Examples


Basic example

A simple component with basic styling for a container and text.


import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const BasicStylingExample = () => {
return (

Welcome to Styling!
This is a basic styled component.

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f0f0f0',
},
headerText: {
fontSize: 28,
fontWeight: 'bold',
color: '#333',
marginBottom: 10,
},
subText: {
fontSize: 16,
color: '#666',
},
});

export default BasicStylingExample;

Real-world example

A user profile card demonstrating layout, images, and conditional styling.


import React, { useState } from 'react';
import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';

const UserProfileCard = () => {
const [isOnline, setIsOnline] = useState(true);

const toggleOnlineStatus = () => {
setIsOnline(!isOnline);
};

return (

source={{ uri: 'https://images.unsplash.com/photo-1535713875002-d1d0cfdfeeab?auto=format&fit=crop&w=400&q=80' }}
style={styles.profileImage}
/>
John Doe
Software Engineer | React Native Enthusiast

{isOnline ? 'Online' : 'Offline'}

Toggle Status


);
};

const styles = StyleSheet.create({
cardContainer: {
backgroundColor: '#fff',
borderRadius: 10,
padding: 20,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 5,
margin: 20,
width: '80%',
},
profileImage: {
width: 100,
height: 100,
borderRadius: 50,
marginBottom: 15,
},
userName: {
fontSize: 22,
fontWeight: 'bold',
color: '#333',
marginBottom: 5,
},
userBio: {
fontSize: 14,
color: '#666',
textAlign: 'center',
marginBottom: 15,
},
statusIndicator: {
width: 12,
height: 12,
borderRadius: 6,
marginBottom: 5,
},
online: {
backgroundColor: 'green',
},
offline: {
backgroundColor: 'gray',
},
statusText: {
fontSize: 14,
color: '#555',
marginBottom: 15,
},
button: {
backgroundColor: '#007bff',
paddingVertical: 10,
paddingHorizontal: 20,
borderRadius: 5,
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
});

export default UserProfileCard;

Advanced usage

Using platform-specific styles and dynamic dimensions.


import React from 'react';
import { View, Text, StyleSheet, Platform, Dimensions } from 'react-native';

const { width, height } = Dimensions.get('window');

const AdvancedStylingExample = () => {
return (


This is running on {Platform.OS === 'ios' ? 'iOS' : 'Android'}!


Screen Width: {width.toFixed(0)}, Height: {height.toFixed(0)}



);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#e8e8e8',
},
platformText: {
fontSize: 20,
fontWeight: 'bold',
color: Platform.OS === 'ios' ? '#007aff' : '#4CAF50', // Blue for iOS, Green for Android
marginBottom: 15,
},
dynamicText: {
fontSize: 16,
color: '#555',
marginBottom: 20,
},
responsiveBox: {
width: width * 0.6, // 60% of screen width
height: height * 0.15, // 15% of screen height
backgroundColor: '#ff6347',
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
},
});

export default AdvancedStylingExample;

Common Mistakes



  • Forgetting StyleSheet.create(): Beginners often define style objects directly without StyleSheet.create(). While it works, it bypasses performance optimizations. Always use StyleSheet.create() for reusable styles.
    Fix: Wrap your style definitions in StyleSheet.create().
    const styles = StyleSheet.create({ container: { /* ... */ } });

  • Using CSS syntax: React Native uses JavaScript object syntax for styles, not standard CSS. Properties like background-color should be backgroundColor.
    Fix: Convert CSS property names to camelCase (e.g., font-size to fontSize, margin-left to marginLeft).

  • Incorrect value types: Many style properties expect specific types (e.g., numbers for dimensions, strings for colors). Using incorrect types can lead to errors or unexpected behavior.
    Fix: Ensure values match expected types. For example, width: '100%' is a string, width: 100 is a number (interpreted as 'dp' or device-independent pixels).


Best Practices



  • Use StyleSheet.create(): Always use StyleSheet.create() for defining styles. This allows React Native to optimize and cache styles, leading to better performance.

  • Separate styles into their own file: For larger components or shared styles, create a separate .js file (e.g., styles.js) to keep your component logic clean and organized.

  • Modularize styles: Break down complex styles into smaller, reusable style objects. This improves readability and maintainability.

  • Use responsive units: Employ Dimensions API for dynamic screen sizes and consider libraries like react-native-responsive-fontsize or react-native-size-matters for more robust responsive design.

  • Leverage conditional styling: Use JavaScript's conditional logic (ternary operators, if statements) to apply styles based on props, state, or platform, making your UI dynamic.


Practice Exercises



  • Create a new React Native component that displays three colored boxes arranged horizontally. Each box should have a different background color and a fixed width and height.

  • Modify the previous exercise to add a text label inside each box. The text should be centered both horizontally and vertically within its box and have a white color.

  • Build a simple button component. It should have a blue background, white text, rounded corners, and padding. When you press the button, nothing needs to happen yet, focus only on its visual appearance.


Mini Project / Task


Create a simple 'Login' screen. It should include a title (e.g., 'Welcome Back!'), two input fields (for username and password), and a 'Login' button. Style all elements using StyleSheet.create(), ensuring the inputs have borders, the button has a distinct background color, and all elements are reasonably spaced and centered on the screen.


Challenge (Optional)


Expand on the 'Login' screen. Implement platform-specific styling such that the input field borders are subtly different on iOS (e.g., lighter gray) compared to Android (e.g., slightly darker gray). Additionally, make the font size of the title responsive, occupying 10% of the screen height, and the button's width 70% of the screen width.

Flexbox in React Native

Flexbox in React Native is the primary layout system used to arrange components on the screen. It exists to make responsive interface design easier across different device sizes and orientations. In real mobile apps, Flexbox is used for common patterns such as centering login forms, building horizontal card rows, spacing buttons evenly, aligning icons with text, and creating dashboard sections. React Native uses a Flexbox model similar to CSS, but with mobile-first defaults. The most important difference is that the default flexDirection is column, which means elements stack vertically unless you change that behavior.

The core properties include flex, flexDirection, justifyContent, alignItems, alignSelf, flexWrap, and gap support depending on version. flexDirection controls whether children are placed in a column, row, reversed column, or reversed row. justifyContent aligns children along the main axis, while alignItems aligns them along the cross axis. flex decides how much space a child should grow to fill compared with its siblings. This makes it easy to distribute available screen space without hardcoding dimensions.

Step-by-Step Explanation

Start by creating a parent View and applying styles through StyleSheet.create(). If you do nothing else, children inside that parent are stacked top to bottom because the default direction is column. To place items side by side, set flexDirection: 'row'. Next, use justifyContent to control spacing on the main axis. For a row, this means horizontal spacing; for a column, vertical spacing. Then use alignItems to align children across the cross axis. Finally, apply flex to children when you want them to expand proportionally. For example, a child with flex: 2 gets twice as much available space as a sibling with flex: 1.

Think in terms of container and children. The container defines layout rules. The children respond to those rules unless they override specific behavior with properties such as alignSelf. This mental model is essential when debugging layout issues.

Comprehensive Code Examples

import React from 'react';import { View, Text, StyleSheet } from 'react-native';export default function App() { return ();}const styles = StyleSheet.create({container: {flex: 1,justifyContent: 'center',alignItems: 'center',},box1: {width: 80,height: 80,backgroundColor: 'tomato',margin: 8,},box2: {width: 80,height: 80,backgroundColor: 'skyblue',margin: 8,},box3: {width: 80,height: 80,backgroundColor: 'limegreen',margin: 8,},});
import React from 'react';import { View, Text, StyleSheet } from 'react-native';export default function App() { return (LogoSearchMenu);}const styles = StyleSheet.create({header: {flexDirection: 'row',justifyContent: 'space-between',alignItems: 'center',padding: 16,backgroundColor: '#fff',},actions: {flexDirection: 'row',gap: 16,},});
import React from 'react';import { View, StyleSheet } from 'react-native';export default function App() { return ();}const styles = StyleSheet.create({container: {flex: 1,flexDirection: 'row',},panel: {height: '100%',},});

Common Mistakes

  • Forgetting the default direction: Beginners expect items to appear in a row. Fix it by setting flexDirection: 'row' when needed.

  • Confusing axes: justifyContent and alignItems affect different directions depending on flexDirection. Always identify the main axis first.

  • Overusing fixed widths and heights: This can break layouts on small or large devices. Prefer flex, padding, and percentage-based sizing where appropriate.

Best Practices

  • Use Flexbox for structure first, then add exact sizing only when necessary.

  • Group related layout rules into reusable style objects for consistency.

  • Test layouts on multiple screen sizes and both portrait and landscape modes.

  • Keep nesting shallow when possible to make layouts easier to understand and maintain.

Practice Exercises

  • Create a screen with three colored boxes stacked vertically and centered on the page.

  • Build a horizontal row of four items with equal spacing between them.

  • Make a two-column layout where the left panel takes one-third of the width and the right panel takes two-thirds.

Mini Project / Task

Build a mobile profile header with an avatar area on the left and a vertical text section on the right, using Flexbox to align content neatly and responsively.

Challenge (Optional)

Create a responsive dashboard section with one large card on top and two smaller cards in a row below it, ensuring spacing and alignment remain clean on different screen sizes.

Colors and Fonts

Colors and fonts are two of the most important parts of mobile app styling in React Native. Colors help users understand actions, status, branding, and visual hierarchy. Fonts control readability, tone, spacing, and the overall personality of an app. In real applications such as banking apps, shopping apps, fitness tools, and chat platforms, a consistent color palette and font system make the interface easier to use and more professional. React Native supports styling through JavaScript objects, so colors and fonts are defined inside style rules instead of traditional CSS files. You can use named colors, hex values, RGB, RGBA, and sometimes platform-supported HSL values, while fonts can be controlled with properties such as fontSize, fontWeight, fontStyle, color, lineHeight, and fontFamily for custom fonts. A good styling system usually includes primary, secondary, background, surface, success, warning, and error colors, along with heading, body, and caption font styles. This structure prevents random values from being repeated across screens. In React Native, colors are often applied to View backgrounds, Text content, borders, buttons, cards, and input fields. Fonts mainly affect Text components because all visible text must be wrapped in Text. Beginners should think of colors and fonts not as decoration, but as a system that improves usability, accessibility, and maintainability.

Step-by-Step Explanation

Start by creating a style object with StyleSheet.create(). To change text color, use the color property inside a text style. To change a component background, use backgroundColor. For fonts, use fontSize for size, fontWeight for thickness, and fontStyle for italic text. Use lineHeight to improve readability for larger text blocks. If you want a custom font, add the font file to your project and reference it with fontFamily. A common professional approach is to store reusable values in separate objects such as colors and fonts, then reference them throughout the app. This makes updates easier because changing one shared value updates many screens. You can also combine multiple style objects in an array, which is useful when a component needs a base style and a special variation.

Comprehensive Code Examples

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export default function App() {
return (

Welcome
Simple color and font styling

);
}

const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5F7FA' },
title: { color: '#2563EB', fontSize: 28, fontWeight: '700' },
subtitle: { color: '#475569', fontSize: 16, marginTop: 8 }
});
const colors = {
primary: '#0EA5E9',
success: '#22C55E',
danger: '#EF4444',
text: '#111827',
muted: '#6B7280',
surface: '#FFFFFF'
};

const fonts = {
heading: { fontSize: 24, fontWeight: '700', color: '#111827' },
body: { fontSize: 16, color: '#374151', lineHeight: 24 },
caption: { fontSize: 12, color: '#6B7280' }
};

const styles = StyleSheet.create({
card: { backgroundColor: colors.surface, padding: 16, borderRadius: 12 },
heading: fonts.heading,
body: fonts.body,
badge: { backgroundColor: colors.success, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 20 }
});
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

export default function ProfileCard() {
const isPremium = true;

return (

Ava Patel
Product Designer

{isPremium ? 'Premium Member' : 'Standard Member'}


);
}

const styles = StyleSheet.create({
card: { padding: 20, margin: 20, backgroundColor: '#FFFFFF', borderRadius: 14 },
name: { fontSize: 22, fontWeight: '700', color: '#0F172A' },
role: { fontSize: 15, color: '#64748B', marginTop: 4 },
status: { marginTop: 12, fontSize: 14, fontWeight: '600' },
premium: { color: '#7C3AED' },
standard: { color: '#334155' }
});

Common Mistakes

  • Using random color values in many files instead of shared constants. Fix: create a central theme object.

  • Applying font styles to View instead of Text. Fix: keep text-related properties on text components.

  • Using very light text on a light background. Fix: check contrast for readability and accessibility.

  • Assuming every fontWeight works with every custom font. Fix: verify available font files and weights.

Best Practices

  • Define reusable color and typography tokens in one file.

  • Use a limited palette to maintain visual consistency.

  • Choose readable font sizes and line heights for long text.

  • Test styles on both Android and iOS because font rendering can differ.

  • Prefer semantic names like primary, textMuted, and headingLg instead of vague names.

Practice Exercises

  • Create a screen with a title, subtitle, and paragraph using three different font sizes and two text colors.

  • Build a reusable colors object with at least five named colors and use them in a card component.

  • Style a warning message using a colored background, bold text, and readable spacing.

Mini Project / Task

Build a small profile summary card with a brand color, heading font, muted secondary text, and a membership label that changes color based on user status.

Challenge (Optional)

Create a simple theme file with light mode colors and typography styles, then refactor two components to use only shared theme values instead of hard-coded styles.

Platform Specific Code


Platform-specific code in React Native refers to the ability to write different pieces of code that execute only on a particular operating system, such as iOS or Android. While React Native aims for maximum code reuse across platforms, there are inevitably situations where you need to tap into native functionalities, UI components, or design patterns that are unique to one platform. This could be due to differences in hardware features (e.g., NFC on Android vs. Apple Pay on iOS), specific UI/UX guidelines (e.g., material design on Android vs. human interface guidelines on iOS), or the availability of certain native modules or APIs. The existence of platform-specific code mechanisms allows developers to achieve a truly native look and feel, optimize performance for specific scenarios, or integrate with device capabilities not yet exposed through React Native's core modules, all while maintaining a predominantly shared codebase. Without these mechanisms, React Native would be limited in its ability to create truly robust and integrated mobile applications.

React Native offers several ways to handle platform-specific code, primarily through two main approaches: using the `Platform` module and using platform-specific file extensions.

The `Platform` module is a core React Native API that detects the operating system on which the app is currently running. It provides properties like `Platform.OS` (which returns 'ios', 'android', or 'web' if using React Native for Web) and `Platform.Version` (the OS version). This allows for conditional rendering or logic within your JavaScript code.

Platform-specific file extensions are a more elegant solution for larger, more complex platform-specific components or modules. You can create files like `MyComponent.ios.js` and `MyComponent.android.js`. When you import `MyComponent.js`, React Native automatically picks the correct file based on the platform. This keeps your main component file cleaner and separates concerns effectively.

Step-by-Step Explanation


Let's break down how to implement platform-specific code.

Using the `Platform` module:
1. Import `Platform` from 'react-native'.
2. Use `Platform.OS` in your conditional logic (e.g., `if (Platform.OS === 'ios') { ... }`).
3. You can also use `Platform.select` for more concise conditional styling or properties. It takes an object where keys are platform names ('ios', 'android') and values are the corresponding platform-specific values.

Using Platform-specific file extensions:
1. Create two separate files for your component or module, for example, `MyButton.ios.js` and `MyButton.android.js`.
2. Ensure both files export a component or function with the same name.
3. In your parent component, simply import `MyButton` (without the extension): `import MyButton from './MyButton';`. React Native's packager will automatically resolve to the correct file.

Comprehensive Code Examples


Basic example (Platform.OS):
import React from 'react';
import { View, Text, Platform, StyleSheet } from 'react-native';

const PlatformSpecificText = () => {
return (


This app is running on {Platform.OS}!

OS Version: {Platform.Version}


{Platform.OS === 'ios' ? (
Hello from iOS!
) : (
Hello from Android!
)}

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: 20,
marginBottom: 10,
},
iosText: {
color: 'blue',
fontSize: 24,
},
androidText: {
color: 'green',
fontSize: 24,
},
});

export default PlatformSpecificText;


Real-world example (Platform.select for styling):
import React from 'react';
import { View, Text, StyleSheet, Platform } from 'react-native';

const MyHeader = () => {
return (

My Awesome App

);
};

const styles = StyleSheet.create({
header: {
paddingTop: Platform.OS === 'ios' ? 40 : 10, // Adjust for iPhone notch
paddingBottom: 10,
backgroundColor: Platform.select({
ios: '#F8F8F8',
android: '#6200EE',
}),
borderBottomWidth: Platform.select({
ios: StyleSheet.hairlineWidth,
android: 0,
}),
borderBottomColor: '#A7A7AA',
alignItems: 'center',
justifyContent: 'center',
height: Platform.select({
ios: 90, // Including status bar height
android: 60,
}),
},
title: {
fontSize: Platform.select({
ios: 22,
android: 24,
}),
fontWeight: Platform.select({
ios: '600',
android: 'bold',
}),
color: Platform.select({
ios: '#000',
android: '#FFF',
}),
},
});

export default MyHeader;


Advanced usage (Platform-specific file extensions):
First, create two files:
components/CustomButton.ios.js:
import React from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';

const CustomButton = ({ title, onPress }) => {
return (

{title} (iOS)

);
};

const styles = StyleSheet.create({
button: {
backgroundColor: '#007AFF',
padding: 15,
borderRadius: 8,
margin: 10,
},
text: {
color: 'white',
fontSize: 18,
textAlign: 'center',
},
});

export default CustomButton;


components/CustomButton.android.js:
import React from 'react';
import { TouchableNativeFeedback, Text, View, StyleSheet } from 'react-native';

const CustomButton = ({ title, onPress }) => {
return (

onPress={onPress}
background={TouchableNativeFeedback.Ripple('#FFF', false)}
>

{title} (Android)



);
};

const styles = StyleSheet.create({
buttonContainer: {
borderRadius: 25,
overflow: 'hidden',
margin: 10,
},
button: {
backgroundColor: '#6200EE',
padding: 15,
borderRadius: 25,
elevation: 3,
minWidth: 150,
},
text: {
color: 'white',< fontSize: 18,
textAlign: 'center',
},
});

export default CustomButton;


Then, import and use it in your `App.js` (or any other component):
import React from 'react';
import { View, Alert } from 'react-native';
import CustomButton from './components/CustomButton'; // No extension needed

const App = () => {
const handlePress = () => {
Alert.alert('Button Pressed!');
};

return (



);
};

export default App;


Common Mistakes


1. Overusing `Platform.OS` conditionals: While useful, sprinkling `if (Platform.OS === 'ios')` throughout your code can make it harder to read and maintain. For significant differences, platform-specific files are often cleaner.
Fix: Refactor complex platform-specific logic or UI into separate files using the `.ios.js` and `.android.js` extensions.
2. Forgetting to import `Platform`: A common oversight leading to runtime errors like "`Platform` is not defined."
Fix: Always ensure `import { Platform } from 'react-native';` is at the top of your file when using the `Platform` module.
3. Incorrect file naming for extensions: Misspelling `.ios.js` or `.android.js` (e.g., `.iphone.js` or `.andoid.js`) will prevent React Native from correctly picking up the platform-specific file.
Fix: Double-check the file names to ensure they exactly match `*.ios.js` and `*.android.js`.

Best Practices



  • Prioritize shared code: Always try to achieve cross-platform compatibility first. Only resort to platform-specific code when truly necessary for native look/feel, performance, or specific device features.

  • Encapsulate platform logic: When using `Platform.OS` or `Platform.select`, try to contain this logic within a custom hook, a utility function, or a dedicated component to avoid scattering conditionals everywhere.

  • Use platform-specific extensions for components: For entire components or modules that have distinct implementations on different platforms, file extensions (`.ios.js`, `.android.js`) are preferred. This keeps your component structure clean and easy to navigate.

  • Test on both platforms: Always test your platform-specific code thoroughly on both iOS and Android emulators/simulators and real devices to ensure it behaves as expected.

  • Document differences: If your application heavily relies on platform-specific implementations, document these differences properly for future maintenance and new team members.


Practice Exercises


1. Create a `StatusBar` component that sets the `backgroundColor` of the `StatusBar` differently for iOS (e.g., 'lightgray') and Android (e.g., 'darkblue'). Hint: You'll need the `StatusBar` component from 'react-native' and the `Platform` module.
2. Build a `GreetingMessage` component. If the app is running on iOS, it should display "Welcome, iOS User!". If on Android, it should display "Greetings, Android User!". Use `Platform.OS` for this.
3. Develop a `ThemeButton` component. Create `ThemeButton.ios.js` and `ThemeButton.android.js`. The iOS version should have a `backgroundColor` of 'purple' and `borderRadius` of 10. The Android version should have a `backgroundColor` of 'orange' and `borderRadius` of 50 (for a circular button). Both should display the text "Press Me".

Mini Project / Task


Create a simple weather app header. This header should display the city name and current temperature. On iOS, the header should have a `height` of 100, `backgroundColor` of '#F0F0F0', and a `shadowOpacity` of 0.2. On Android, the header should have a `height` of 70, `backgroundColor` of '#4CAF50', and `elevation` of 4. Use `Platform.select` for styling properties and ensure the text color adjusts appropriately for contrast on both platforms.

Challenge (Optional)


Implement a custom `ShareButton` component. On iOS, this button should use `Share.share()` from 'react-native' to share a text message. On Android, it should also use `Share.share()`, but additionally, try to conditionally add a more robust sharing option if `Platform.Version` indicates Android 10 (API level 29) or higher, perhaps by logging a message to the console indicating a more advanced sharing API could be used. Ensure the button text clearly indicates which platform's sharing is being invoked.

State with useState



In React Native, just like in React, 'state' is a plain JavaScript object used to store data that can change over time and influence the rendering of a component. When a component's state changes, React Native re-renders the component to reflect those changes. This mechanism is fundamental for creating dynamic and interactive user interfaces. Without state, your components would be static and unable to respond to user input or external data.

The `useState` Hook is the standard way to add state to functional components in React Native (and React). Before Hooks, state was primarily managed using class components. `useState` simplifies state management by allowing functional components to hold and update their own local state without needing to convert them into class components. This leads to cleaner, more readable, and often more performant code.

You'll find state extensively used in almost every interactive part of a mobile application. For instance, managing the text input in a search bar, tracking whether a user is logged in, toggling the visibility of a modal, updating a counter, or storing data fetched from an API are all common real-world scenarios where `useState` is indispensable. It's the backbone for any dynamic UI element.

Step-by-Step Explanation


The `useState` Hook returns an array with two elements: the current state value and a function to update it. Here's how to use it:

1. Import `useState`: You need to import `useState` from the 'react' library at the top of your component file.
import React, { useState } from 'react';

2. Declare a state variable: Inside your functional component, call `useState` with the initial state value as its argument.
const [count, setCount] = useState(0);
- `count`: This is your state variable. It holds the current value of the state.
- `setCount`: This is the updater function. You call this function to update the `count` state. When `setCount` is called, React Native will re-render the component with the new `count` value.
- `0`: This is the initial value for `count`. It can be any valid JavaScript data type (number, string, boolean, object, array, null).

3. Use the state variable: You can use the `count` variable directly in your JSX to display its value.
Current count: {count}

4. Update the state: Call the updater function (`setCount`) to change the state. When you call `setCount`, the component will re-render.

Props and Component Communication

Props are the main way components talk to each other in React Native. A parent component sends data to a child component through attributes called props, and the child uses that data to render UI or trigger behavior. This exists because mobile apps are built from many small, reusable pieces such as buttons, cards, headers, product rows, and forms. Instead of hardcoding values inside each component, props let you pass dynamic data like names, prices, images, labels, styles, and event handlers. In real apps, props are used everywhere: a shopping app passes product info into a product card, a chat app passes message text into a message bubble, and a settings screen passes a toggle value and callback into a switch row. Component communication usually follows one-way data flow: data moves down from parent to child, while child-to-parent communication happens by calling a function prop provided by the parent. Common prop patterns include primitive props such as strings and numbers, object props for grouped data, boolean props for flags, and function props for events like button presses.

Step-by-Step Explanation

To use props, first create a child component that accepts a props object or uses destructuring. Next, render values from those props inside React Native elements like Text, View, and Pressable. Then, in the parent component, include the child component and pass values using JSX attributes. For event communication, define a function in the parent and pass it down as a prop. The child calls that function when something happens, such as a press event. This keeps state in the parent while allowing the child to stay reusable. You can also pass arrays and objects, but be careful to use the correct property names. If a prop is optional, provide a fallback value during destructuring or with simple conditional rendering.

Comprehensive Code Examples

Basic example
import React from 'react';
import { View, Text } from 'react-native';

const Greeting = ({ name }) => {
return (

Hello, {name}!

);
};

export default function App() {
return ;
}
Real-world example
import React from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';

const ProductCard = ({ title, price, imageUrl, inStock }) => {
return (


{title}
${price}
{inStock ? 'Available' : 'Out of stock'}

);
};

const styles = StyleSheet.create({
card: { padding: 12, backgroundColor: '#fff', borderRadius: 8 },
image: { width: 120, height: 120 },
title: { fontWeight: 'bold', marginTop: 8 }
});

export default function App() {
return (
title="Wireless Headphones"
price={89.99}
imageUrl="https://images.unsplash.com/photo-1505740420928-5e560c06d30e?auto=format&fit=crop&w=400&q=80"
inStock={true}
/>
);
}
Advanced usage
import React, { useState } from 'react';
import { View, Text, Pressable } from 'react-native';

const CounterControls = ({ count, step = 1, onIncrement, onReset }) => {
return (

Count: {count}
onIncrement(step)}>Increase
Reset

);
};

export default function App() {
const [count, setCount] = useState(0);

const handleIncrement = (value) => setCount(prev => prev + value);
const handleReset = () => setCount(0);

return (
count={count}
step={2}
onIncrement={handleIncrement}
onReset={handleReset}
/>
);
}

Common Mistakes

  • Using the wrong prop name: Passing username but reading name. Fix by matching names exactly.
  • Trying to change props inside a child: Props are read-only. Fix by updating state in the parent and passing new values down.
  • Forgetting curly braces for non-string values: Use price={10}, not price="10" if you need a number.
  • Calling a function immediately: Use onPress={() => onIncrement(step)} instead of invoking it during render.

Best Practices

  • Keep props small and meaningful so components remain easy to understand.
  • Destructure props in the function signature for cleaner code.
  • Use function props for upward communication instead of tightly coupling components.
  • Provide sensible default values for optional props.
  • Prefer reusable presentational components that receive data through props rather than fetching their own data unnecessarily.

Practice Exercises

  • Create a UserBadge component that receives name and role as props and displays them.
  • Build a StatusMessage component that receives a boolean prop and shows different text depending on the value.
  • Create a parent component with a button and pass a function prop to a child so the child can increase a counter in the parent.

Mini Project / Task

Build a simple contact card screen where a parent component passes a contact name, phone number, and favorite status to a reusable ContactCard component, along with a function prop to toggle the favorite label.

Challenge (Optional)

Create a reusable CustomButton component that accepts label, disabled state, background color, and an onPress function, then use it in multiple places with different prop values.

useEffect in React Native

useEffect is a React Hook used in React Native functional components to run side effects after rendering. A side effect is anything that happens outside simply returning UI, such as fetching data from an API, starting a timer, listening for network changes, subscribing to app state events, updating the screen title, or saving values to local storage. In real mobile apps, useEffect is commonly used when a screen loads user data, refreshes content when a value changes, or cleans up resources when a screen is closed. It exists because rendering should stay focused on describing the interface, while side-effect logic should be managed separately in a predictable way.

There are several common patterns. A useEffect with no dependency array runs after every render. A useEffect with an empty dependency array runs only once after the first render, which is useful for initial setup. A useEffect with specific dependencies runs when one of those values changes. A useEffect can also return a cleanup function, which runs before the effect runs again or when the component unmounts. Cleanup is important in mobile apps to prevent memory leaks from timers, listeners, and subscriptions.

Step-by-Step Explanation

First, import useEffect and usually useState from React. Next, place useEffect(() => { ... }, [dependencies]) inside your component. The first argument is a function containing the side-effect logic. The second argument is the dependency array. If you pass [], the effect runs once after the component appears. If you pass [count], it runs after the first render and whenever count changes. If you omit the array, it runs after every render. If your effect starts something that must be stopped later, return a function like return () => { ... }.

Comprehensive Code Examples

Basic example:

import React, { useEffect } from 'react';
import { View, Text } from 'react-native';

const WelcomeScreen = () => {
useEffect(() => {
console.log('Screen mounted');
}, []);

return (

Welcome to React Native

);
};

export default WelcomeScreen;

Real-world example:

import React, { useEffect, useState } from 'react';
import { View, Text, ActivityIndicator } from 'react-native';

const UserScreen = () => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchUser = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
const data = await response.json();
setUser(data);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
}
};

fetchUser();
}, []);

if (loading) return ;

return (

{user.name}
{user.email}

);
};

export default UserScreen;

Advanced usage:

import React, { useEffect, useState } from 'react';
import { View, Text, Button } from 'react-native';

const TimerScreen = () => {
const [seconds, setSeconds] = useState(0);
const [running, setRunning] = useState(false);

useEffect(() => {
if (!running) return;

const interval = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);

return () => clearInterval(interval);
}, [running]);

return (

Seconds: {seconds}

Common Mistakes

  • Forgetting the dependency array: this can cause repeated API calls on every render. Add [] or the correct dependencies.
  • Missing cleanup: timers or listeners may continue running after leaving a screen. Return a cleanup function.
  • Using stale values: effects may read old state if dependencies are incomplete. Include all required values in the dependency array.
  • Updating state in an endless loop: setting state inside an effect that depends on that same state can re-run forever. Recheck dependencies and logic.

Best Practices

  • Keep each effect focused on one responsibility, such as data fetching or subscriptions.
  • Always think about when the effect should run: once, on change, or every render.
  • Use cleanup for intervals, event listeners, and subscriptions.
  • Prefer async helper functions inside the effect instead of making the effect callback itself async.
  • Split large effects into smaller ones for easier testing and maintenance.

Practice Exercises

  • Create a screen that logs a message only once when the component loads.
  • Build a counter screen where a message prints whenever the counter value changes.
  • Create a timer that starts on mount and stops automatically when the component unmounts.

Mini Project / Task

Build a weather screen that fetches city weather data when the screen opens, shows a loading indicator, displays the result, and cleans up any timer used for auto-refresh.

Challenge (Optional)

Create a search screen where data is fetched whenever the search term changes, but avoid unnecessary requests by adding a short delay before calling the API.

useRef and useMemo



The React Native ecosystem, built upon React, provides powerful hooks to manage component state, lifecycle, and performance. Among these, <_strong>useRef and <_strong>useMemo are crucial for optimizing performance and interacting with the underlying DOM (or in React Native's case, the native views) directly. Understanding when and how to use these hooks can significantly improve your application's responsiveness and efficiency.

In essence, <_strong>useRef is a hook that allows you to persist mutable values between renders without causing re-renders of the component. It's often used for direct manipulation of child components or native views, accessing DOM elements, or storing any mutable value that doesn't need to trigger a re-render. Think of it as a persistent box where you can keep anything that should survive across renders but doesn't need to be part of the component's reactive state.

<_strong>useMemo, on the other hand, is a hook used for memoization. Memoization is an optimization technique that stores the result of an expensive function call and returns the cached result when the same inputs occur again. In React Native, <_strong>useMemo helps prevent unnecessary re-calculations of values that are passed down to child components or used within the current component, thus optimizing performance by avoiding redundant work during re-renders.

These hooks are used extensively in real-world React Native applications. For instance, <_strong>useRef is indispensable for tasks like managing focus on a <_code>TextInput, triggering imperative animations, or integrating with third-party libraries that require direct access to native view instances. <_strong>useMemo is vital for optimizing complex calculations, filtering large datasets, or preventing unnecessary re-renders of expensive child components by memoizing props or values.

Core Concepts & Sub-types



While <_strong>useRef and <_strong>useMemo don't have 'sub-types' in the conventional sense, they represent distinct approaches to optimization and interaction.

<_strong>useRef:
Its primary use cases involve:
  • <_strong>Accessing Native View Instances: In React Native, you can attach a ref to a component (like <_code>TextInput or <_code>ScrollView) to get a direct handle to its underlying native instance. This allows you to call imperative methods on that instance, such as <_code>focus(), <_code>blur(), or <_code>scrollTo().
  • <_strong>Storing Mutable Values: You can use a ref to store any mutable value that you want to persist across renders without triggering a re-render. This is useful for timers, counters, or flags that don't directly affect the UI's rendering logic.
  • <_strong>Integrating with Third-Party DOM/Native Libraries: When working with libraries that expect a direct reference to a native element, <_strong>useRef provides the necessary mechanism.

<_strong>useMemo:
Its core function is memoization, which applies to various scenarios:
  • <_strong>Memoizing Expensive Calculations: If you have a function that performs a complex calculation and its result is only dependent on specific inputs, <_strong>useMemo can cache the result.
  • <_strong>Memoizing Component Props: When passing complex objects or arrays as props to child components, <_strong>useMemo can ensure that these props are only re-created if their underlying dependencies change, preventing unnecessary re-renders of the child component (especially when used with <_code>React.memo).
  • <_strong>Memoizing Callback Functions (useCallback): While not <_strong>useMemo directly, <_code>useCallback is a specialized version of <_strong>useMemo for memoizing functions themselves. This is crucial for optimizing child components that rely on referential equality for props.

Step-by-Step Explanation



<_strong>useRef:
1. <_strong>Import: Import <_code>useRef from 'react'.
2. <_strong>Initialize: Call <_code>useRef() at the top level of your component. It takes an optional initial value. It returns a mutable ref object whose <_code>.current property is initialized to the passed argument (or <_code>null if no argument is passed).
3. <_strong>Access/Assign: The <_code>.current property of the ref object can be directly read and modified. Changes to <_code>.current do not trigger re-renders.
4. <_strong>Attach to Component: To connect a ref to a React Native component (like <_code>TextInput), pass the ref object to the <_code>ref prop of that component.

<_strong>useMemo:
1. <_strong>Import: Import <_code>useMemo from 'react'.
2. <_strong>Call: Call <_code>useMemo(callback, dependencies) at the top level of your component.
3. <_strong>Callback Function: The first argument is a function that computes the value you want to memoize. This function will only run if the dependencies change.
4. <_strong>Dependencies Array: The second argument is an array of dependencies. If any value in this array changes between renders, the <_code>callback function will re-execute, and the new result will be memoized. If the array is empty (<_code>[]), the callback runs only once after the initial render.

Comprehensive Code Examples



<_strong>Basic useRef Example: Focusing a TextInput
<_code data-lang='React Native'>import React, { useRef } from 'react';
import { View, TextInput, Button, StyleSheet } from 'react-native';

function FocusInputExample() {
const inputRef = useRef(null);

const handleFocus = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};

return (

ref={inputRef}
style={styles.input}
placeholder="Type here..."
/>


<_strong>Basic useMemo Example: Memoizing a calculation
<_code data-lang='React Native'>import React, { useState, useMemo } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

function ExpensiveCalculationComponent() {
const [count, setCount] = useState(0);
const [anotherCount, setAnotherCount] = useState(0);

// This function simulates an expensive calculation
const calculateExpensiveValue = (num) => {
console.log('Calculating expensive value...');
let sum = 0;
for (let i = 0; i < 100000000; i++) {
sum += num;
}
return sum;
};

// The expensive value is only re-calculated when 'count' changes
const memoizedExpensiveValue = useMemo(() => calculateExpensiveValue(count), [count]);

return (

Count: {count}


<_strong>Real-world Example: ScrollView Imperative Scrolling with useRef and Memoized Item List with useMemo
<_code data-lang='React Native'>import React, { useState, useRef, useMemo } from 'react';
import { View, Text, ScrollView, Button, StyleSheet } from 'react-native';

const generateItems = (numItems) => {
console.log('Generating items...');
const items = [];
for (let i = 0; i < numItems; i++) {
items.push({ id: i, text: `Item ${i + 1}` });
}
return items;
};

function ShoppingList() {
const scrollViewRef = useRef(null);
const [numItems, setNumItems] = useState(100);
const [filterText, setFilterText] = useState('');

// Memoize the list of items based on numItems
const allItems = useMemo(() => generateItems(numItems), [numItems]);

// Memoize the filtered list based on allItems and filterText
const memoizedFilteredItems = useMemo(() => {
console.log('Filtering items...');
return allItems.filter(item => item.text.toLowerCase().includes(filterText.toLowerCase()));
}, [allItems, filterText]);

const scrollToTop = () => {
scrollViewRef.current?.scrollTo({ y: 0, animated: true });
};

const scrollToEnd = () => {
scrollViewRef.current?.scrollToEnd({ animated: true });
};

return (

Shopping List ({memoizedFilteredItems.length} items)

style={styles.input}
placeholder="Filter items..."
value={filterText}
onChangeText={setFilterText}
/>




<_strong>Advanced Usage: Storing a mutable counter with useRef without re-rendering
<_code data-lang='React Native'>import React, { useState, useRef } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

function CounterWithRef() {
const [renderCount, setRenderCount] = useState(0);
const mutableCounter = useRef(0); // This value won't trigger re-renders

const incrementMutableCounter = () => {
mutableCounter.current += 1;
console.log('Mutable counter:', mutableCounter.current);
// UI will not update for mutableCounter unless renderCount changes
};

const forceRerender = () => {
setRenderCount(renderCount + 1);
};

return (

Component Render Count: {renderCount}


Common Mistakes



  • <_strong>Using <_code>useRef to store values that should trigger re-renders: If you want a value to be displayed in the UI and cause updates when it changes, use <_code>useState instead of <_code>useRef. <_code>useRef is for mutable values that don't directly affect rendering.
    <_strong>Fix: For UI-dependent state, use <_code>const [value, setValue] = useState(initialValue).
  • <_strong>Directly modifying <_code>useRef().current during render: Modifying <_code>ref.current during the render phase can lead to unpredictable behavior and is generally an anti-pattern. Refs should primarily be assigned or read in event handlers, <_code>useEffect callbacks, or other non-render contexts.
    <_strong>Fix: Perform ref assignments or modifications inside event handlers or <_code>useEffect hooks.
  • <_strong>Incorrect dependency array for <_code>useMemo (either missing or too broad): A missing dependency array (<_code>useMemo(() => ..., )) will cause the value to be re-calculated on every render. An empty array (<_code>useMemo(() => ..., [])) means it will only calculate once. A too-broad array will cause unnecessary re-calculations.
    <_strong>Fix: Always provide a dependency array. Include all variables from the component scope that are used inside the <_code>useMemo callback and whose changes should trigger a re-calculation.
  • <_strong>Overusing <_code>useMemo for trivial calculations: Memoization itself has a cost (memory and comparison checks). Applying <_code>useMemo to simple calculations that are cheaper to re-run than to memoize can actually decrease performance.
    <_strong>Fix: Use <_code>useMemo only for computationally expensive operations or to prevent unnecessary re-renders of child components due to prop identity changes. Profile your application to identify bottlenecks before applying <_code>useMemo widely.

Best Practices



  • <_strong>For <_code>useRef for native view interaction: Always check if <_code>ref.current is not null before attempting to use it, as the ref might not be attached yet, especially during initial renders or when components unmount. Use optional chaining (<_code>ref.current?.method()) for safety.
  • <_strong>For <_code>useRef for mutable values: Use it for values that are internal to the component's logic and whose changes should not trigger a re-render, like timer IDs, mutable objects that are not state, or previous state values.
  • <_strong>For <_code>useMemo, be explicit with dependencies: The dependencies array for <_code>useMemo (and <_code>useCallback) should be complete. If you omit a dependency, your memoized value might become stale. If you include too many, you might defeat the purpose of memoization. ESLint's <_code>exhaustive-deps rule is very helpful here.
  • <_strong>Combine <_code>useMemo with <_code>React.memo for component optimization: To prevent unnecessary re-renders of child components, memoize complex props (objects, arrays, functions) passed to them using <_code>useMemo (or <_code>useCallback). Then, wrap the child component with <_code>React.memo to ensure it only re-renders when its props actually change.
  • <_strong>Profile before optimizing: Don't prematurely optimize with <_code>useMemo. Use React DevTools profiler to identify performance bottlenecks first. Applying <_code>useMemo indiscriminately can add unnecessary overhead.

Practice Exercises



1. <_strong>Ref-controlled Counter: Create a component with a button and a <_code>Text component. Use <_code>useRef to store a counter value that increments when the button is pressed. The <_code>Text component should display the current <_code>ref.current value. Add another button that uses <_code>useState to force a re-render, thus updating the displayed <_code>ref.current value.
2. <_strong>Memoized List Filtering: Build a React Native component that displays a list of 1000 randomly generated numbers. Include a <_code>TextInput to filter these numbers (e.g., show only numbers greater than a certain value). Use <_code>useMemo to optimize the filtering process so that the list is only re-filtered when the list of numbers or the filter criteria changes.
3. <_strong>Auto-scrolling Chat: Create a simple chat-like interface using a <_code>ScrollView and a button to add new messages. Use <_code>useRef to get a reference to the <_code>ScrollView and automatically scroll to the bottom whenever a new message is added.

Mini Project / Task



Build a simple product details screen in React Native. This screen should display a product image, title, description, and a list of reviews. Implement the following:
  • Use <_code>useRef to manage the focus of a <_code>TextInput for adding a new review. When a button is pressed, the <_code>TextInput should automatically gain focus.
  • Use <_code>useMemo to calculate the average rating of the product based on the list of reviews. Ensure this calculation only re-runs when the list of reviews changes, not on other state updates in the component.

Challenge (Optional)



Extend the product details screen. Create a 'Share' button. When pressed, this button should imperatively trigger a native share sheet using the <_code>Share API from 'react-native' (or a similar library). The data shared should include the product title and average rating. Consider how <_code>useRef might be used if you were integrating with a more complex native module that required a direct view reference for its operations, even if not strictly necessary for the <_code>Share API itself.

Custom Hooks

Custom Hooks are reusable JavaScript functions that let you extract and share stateful logic between React Native components. They exist because many mobile apps repeat the same patterns: fetching API data, tracking network status, debouncing search input, managing form fields, or listening to app lifecycle changes. Instead of copying the same useState and useEffect code into many screens, you can place that logic inside a custom Hook and reuse it cleanly. In real-life React Native projects, custom Hooks are often used for authentication state, device permissions, geolocation, pagination, caching, and input validation. A custom Hook is simply a function whose name starts with use and which can call other Hooks inside it. Some Hooks return values, some return functions, and many return both. Common sub-types include state Hooks such as useToggle, data Hooks such as useFetchUsers, and utility Hooks such as useDebounce. This pattern improves readability, reduces duplication, and makes large codebases easier to test and maintain.

Step-by-Step Explanation

To create a custom Hook, start with a function named like useSomething. Inside it, use built-in Hooks such as useState, useEffect, useMemo, or useCallback. Then return the state and actions the component needs. For beginners, think of it as moving repeated logic out of a screen and wrapping it in a reusable function. Example flow: define state, perform side effects if needed, create helper functions, and return everything in an object or array. Components then call the Hook at the top level just like built-in Hooks. Remember the Rules of Hooks: call Hooks only at the top level and only inside React components or other Hooks. Do not call a custom Hook inside conditions, loops, or nested functions.

Comprehensive Code Examples

Basic example: toggle state

import React from 'react';
import { View, Text, Button } from 'react-native';

function useToggle(initialValue = false) {
const [value, setValue] = React.useState(initialValue);
const toggle = () => setValue(current => !current);
return { value, toggle, setValue };
}

export default function App() {
const { value, toggle } = useToggle(false);

return (

{value ? 'Enabled' : 'Disabled'}

Real-world example: fetch remote data

import React from 'react';
import { View, Text, ActivityIndicator } from 'react-native';

function useUsers() {
const [users, setUsers] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState(null);

React.useEffect(() => {
let mounted = true;

async function loadUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
if (mounted) setUsers(data);
} catch (err) {
if (mounted) setError('Failed to load users');
} finally {
if (mounted) setLoading(false);
}
}

loadUsers();
return () => { mounted = false; };
}, []);

return { users, loading, error };
}

Advanced usage: debounced search

import React from 'react';

function useDebounce(value, delay = 500) {
const [debouncedValue, setDebouncedValue] = React.useState(value);

React.useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);

return debouncedValue;
}

Common Mistakes

  • Calling a Hook conditionally: always call custom Hooks at the top level of the component.
  • Forgetting the use prefix: without it, the function is not clearly recognized as a Hook.
  • Returning too little or too much: return only the data and actions the component truly needs.
  • Ignoring cleanup: clear timers, subscriptions, or async side effects inside useEffect cleanup.

Best Practices

  • Keep each custom Hook focused on one responsibility.
  • Use descriptive names like useAuth, useForm, or useOnlineStatus.
  • Return objects when there are multiple values for better readability.
  • Document expected inputs and outputs clearly.
  • Test Hook logic independently when possible.

Practice Exercises

  • Create a useCounter Hook with increment, decrement, and reset functions.
  • Build a useInput Hook that stores text and returns a change handler.
  • Create a useLoading Hook that exposes loading state and functions to start or stop loading.

Mini Project / Task

Build a small React Native profile screen that uses a custom Hook to fetch user data, show a loading spinner, and display an error message if the request fails.

Challenge (Optional)

Create a useSearch Hook that combines local input state, debouncing, and filtered results for a list of items.

Navigation Overview

Navigation in React Native is the system that lets users move between screens such as Home, Profile, Settings, Details, and Checkout. In real mobile apps, navigation is one of the most important building blocks because almost every app contains multiple screens and user flows. A shopping app may move from a product list to product details and then to payment. A social app may switch between feed, messages, notifications, and user profiles. React Native itself does not provide a complete built-in navigation framework for app-level routing, so developers commonly use libraries such as React Navigation. The main goal of navigation is to manage screen transitions, preserve history, pass data between screens, and create familiar mobile patterns. Common navigation styles include stack navigation for drill-down flows, tab navigation for switching between major sections, and drawer navigation for side menus. These patterns can also be combined in larger apps. Understanding navigation helps beginners structure apps correctly instead of placing everything inside a single screen. It also improves user experience because users expect back buttons, screen titles, and predictable movement through the app.

Step-by-Step Explanation

To use navigation, developers usually install @react-navigation/native and a navigator package such as @react-navigation/native-stack. The app is wrapped in NavigationContainer, which manages the navigation tree. Inside it, you create a navigator, define screens, and map each screen name to a React component. A stack navigator behaves like a pile of cards: when you open a new screen, it is pushed on top; when you go back, it is popped off. You move between screens with methods such as navigation.navigate('Details') or navigation.goBack(). You can also send parameters like an item ID using navigation.navigate('Details', { id: 5 }), then read them from route.params. Tabs work differently: instead of moving deeper, users switch between top-level areas. Drawers slide in from the side and are useful for app-wide links. Beginners should remember that screen names must match exactly, navigators should be declared clearly, and nested navigators are common in professional apps.

Comprehensive Code Examples

import React from 'react';
import { View, Text, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

function HomeScreen({ navigation }) {
return (

Home Screen
function ProductListScreen({ navigation }) {
return (

title="Open Product 101"
onPress={() => navigation.navigate('ProductDetails', { productId: 101, name: 'Headphones' })}
/>

);
}

function ProductDetailsScreen({ route }) {
const { productId, name } = route.params;
return {name} - #{productId};
}
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

function FeedScreen() { return Feed; }
function SettingsScreen() { return Settings; }

const Tab = createBottomTabNavigator();

function MainTabs() {
return (




);
}

Common Mistakes

  • Forgetting NavigationContainer: Without it, navigation will not work. Wrap your root navigator correctly.
  • Mismatched screen names: If you call navigate('Detail') but the screen is Details, navigation fails. Keep names consistent.
  • Not checking route params: Accessing missing params can cause errors. Validate values before using them.

Best Practices

  • Use stack navigation for detail flows, tabs for major app sections, and drawers only when they truly improve access.
  • Keep screen components small and focused so navigation logic stays clean.
  • Use descriptive route names such as ProductDetails instead of vague names like Page2.
  • Group related navigators to support scaling and nested app structures.

Practice Exercises

  • Create a stack navigator with two screens: Home and About. Add a button to move from Home to About.
  • Pass a username from one screen to another and display it on the second screen.
  • Create a bottom tab navigator with three tabs: Feed, Search, and Profile.

Mini Project / Task

Build a small shopping flow with a Home screen, a Product Details screen, and a Cart screen. Use stack navigation and pass the product name from Home to Product Details.

Challenge (Optional)

Create an app with bottom tabs where one tab contains its own stack navigator. For example, the Home tab should open a Details screen while the Settings tab remains separate.

React Navigation Setup

React Navigation is the standard library used to move between screens in many React Native apps. It exists because mobile applications rarely live on a single screen. Real apps need flows such as login, home, profile, settings, product details, and checkout. React Navigation provides a structured way to define those flows while keeping code modular and readable. In real life, developers use it in e-commerce apps for product browsing, in banking apps for account sections, and in social apps for profile and chat screens. The main navigation patterns include stack navigation for moving forward and backward between screens, tab navigation for switching between major sections, and drawer navigation for side menus. These patterns can also be combined, such as tabs inside a stack or a stack inside a drawer, depending on app complexity.

To set it up, you first install the core package and required dependencies. In current React Native projects, the most common starting point is a native stack navigator because it gives screen transitions and header behavior similar to real mobile apps. After installation, the whole app must be wrapped with NavigationContainer. This container manages the navigation tree and app state. Then you create a navigator, register screens, and choose which screen opens first. Each screen is just a React component. Once registered, you can move between them using the navigation object passed into each screen component.

Step-by-Step Explanation

Step 1: Install packages. Add @react-navigation/native and a navigator package such as @react-navigation/native-stack. Most React Native apps also need supporting libraries like react-native-screens, react-native-safe-area-context, and sometimes gesture-related packages depending on navigation type.
Step 2: Import tools. Import NavigationContainer and createNativeStackNavigator.
Step 3: Create screen components. These can be simple functions returning UI.
Step 4: Create the stack object with createNativeStackNavigator().
Step 5: Wrap navigators inside NavigationContainer.
Step 6: Add screens using Stack.Screen with a unique name and a component.
Step 7: Navigate by calling navigation.navigate('ScreenName').
Step 8: Configure options like titles, hidden headers, or initial route when needed.

Comprehensive Code Examples

import React from 'react'; import { View, Text, Button } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; const Stack = createNativeStackNavigator(); function HomeScreen({ navigation }) { return ( Home
import React from 'react'; import { View, Text, Button } from 'react-native'; function ProductListScreen({ navigation }) { return ( Products
import React from 'react'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; const Stack = createNativeStackNavigator(); const Tab = createBottomTabNavigator(); function HomeTabs() { return (     ); } export default function App() { return (       ); }

Common Mistakes

  • Forgetting NavigationContainer: Without it, navigation will not work. Always wrap your top-level navigator.
  • Using the wrong screen name: navigation.navigate('Detail') will fail if the registered name is Details. Match names exactly.
  • Not installing dependencies fully: Some navigators require additional native packages. Follow the package installation guide carefully.
  • Passing params incorrectly: Access route values from route.params, not directly from props.

Best Practices

  • Keep navigation structure in a dedicated file such as navigation/AppNavigator.js.
  • Use clear, predictable screen names like Home, Profile, and Settings.
  • Choose the simplest navigator that matches the user flow.
  • Hide headers only when you truly need custom UI.
  • Group related screens into nested navigators for better scalability.

Practice Exercises

  • Create a stack navigator with two screens: Welcome and About. Add a button to move from Welcome to About.
  • Build a three-screen flow: Login, Dashboard, and Profile. Make each screen navigate to the next.
  • Pass a username from one screen to another and display it using route.params.

Mini Project / Task

Build a small shopping app navigation setup with a Home screen, a ProductDetails screen, and a Cart screen. Use stack navigation and pass a product ID into the details screen.

Challenge (Optional)

Create a nested navigation structure where a bottom tab navigator contains Home and Account, and the Home tab can open a stack-based Details screen.

Stack Navigator

Stack Navigator is a navigation pattern where screens are arranged like a stack of cards. When a user opens a new screen, it is pushed on top of the stack. When they go back, the top screen is popped off. This pattern is common in real apps such as product details from a catalog, message threads from an inbox, or profile settings from an account page. In React Native, Stack Navigator is typically created with React Navigation and gives your app a familiar mobile experience with headers, back buttons, screen transitions, and route parameters.

Why does it exist? Mobile apps rarely stay on one screen. Users expect smooth movement between related pages while preserving history. Stack navigation solves this by keeping track of the order of visited screens. Common concepts include a navigator, screens, route params, navigation actions, and header options. You will often use actions like navigation.navigate(), navigation.push(), navigation.goBack(), and navigation.replace(). A useful distinction is that navigate goes to a screen and may reuse an existing route, while push always adds a new instance on top. replace swaps the current screen, which is useful after login flows.

Step-by-Step Explanation

First, install navigation packages and wrap your app with NavigationContainer. Next, create a stack using createNativeStackNavigator(). Then register each screen with a unique name. Each screen component receives navigation and route props. Use the navigation object to move between screens and the route.params object to read passed data. You can also configure shared options in the navigator, such as header style, title, and animation, or per-screen options using the options prop. For beginners, think of the syntax as three layers: app container, stack definition, and screen logic.

Comprehensive Code Examples

Basic example
import React from 'react';
import { Button, Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

function HomeScreen({ navigation }) {
return (

Home Screen
Real-world example
function ProductsScreen({ navigation }) {
return (

title="Open Phone Details"
onPress={() => navigation.navigate('ProductDetails', { id: 101, name: 'Phone' })}
/>

);
}

function ProductDetailsScreen({ route, navigation }) {
const { id, name } = route.params;
return (

Product: {name}
ID: {id}
Advanced usage
function LoginScreen({ navigation }) {
return (

Common Mistakes

  • Forgetting NavigationContainer: Without it, navigation will not work. Always wrap your root navigator.
  • Using the wrong screen name: navigation.navigate('Detail') will fail if the registered name is Details.
  • Accessing missing params: If no params were passed, route.params.id may crash. Check values or provide defaults.

Best Practices

  • Use clear and consistent screen names such as Home, Details, Profile, and Settings.
  • Pass only necessary params, not large objects, to keep navigation lightweight.
  • Use replace for authentication transitions so users cannot go back to login unnecessarily.
  • Centralize header styling with screenOptions for a consistent app look.

Practice Exercises

  • Create a stack with Home and About screens, then add a button to move between them.
  • Build a list screen and a detail screen, and pass a product name as a route param.
  • Add a custom title to one screen and a back button flow using goBack().

Mini Project / Task

Build a simple recipe app with three screens: Recipe List, Recipe Details, and Favorites. Use stack navigation to open recipe details and pass the selected recipe name and ID between screens.

Challenge (Optional)

Create a login flow where Login is replaced by Dashboard after success, then allow users to push multiple Profile screens for different team members and navigate back through the history correctly.

Tab Navigator

A Tab Navigator is a navigation pattern that lets users switch between major screens by tapping tabs, usually shown at the bottom of a mobile app or sometimes at the top. In React Native, tab navigation is commonly built with React Navigation, and it is widely used in real apps such as social media, shopping, banking, and music platforms. For example, an app may have tabs like Home, Search, Cart, and Profile. This pattern exists because users need fast access to primary areas of an application without repeatedly opening menus or going back through screen stacks. In React Native, the most common tab types are bottom tabs and material top tabs. Bottom tabs are ideal for core app sections and feel natural on mobile devices. Top tabs are often used inside a screen to switch between related views, such as Posts and Likes on a profile page. A Tab Navigator works by registering screens as routes and rendering the currently selected one while keeping the tab bar visible for quick switching.

To use it, developers usually install React Navigation and the bottom tabs package. Each tab screen can have a label, icon, custom style, and behavior. Tabs can also contain nested navigators, which means a tab might open a stack of screens inside it. This is common in production apps, where the Home tab may contain a Home screen, Details screen, and Settings screen, all managed inside one tab. Understanding this structure is important because beginners often confuse a tab screen with a single component, while in practice each tab can represent an entire navigation flow.

Step-by-Step Explanation

First, import NavigationContainer from React Navigation and createBottomTabNavigator from the bottom tabs package. Second, create screen components such as HomeScreen and ProfileScreen. Third, call createBottomTabNavigator() to create a Tab object. Fourth, wrap your navigator with NavigationContainer. Fifth, define each tab using Tab.Screen and give it a unique name and component. You can customize the tab bar using the screenOptions prop, where you set labels, colors, icons, and styles. If you want icons, you typically use a library such as Expo Vector Icons or React Native Vector Icons. You can also hide headers, set the active tint color, or conditionally hide the tab bar on specific nested screens.

Comprehensive Code Examples

import React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

function HomeScreen() {
return Home;
}

function ProfileScreen() {
return Profile;
}

const Tab = createBottomTabNavigator();

export default function App() {
return (






);
}
import Ionicons from '@expo/vector-icons/Ionicons';

screenOptions={({ route }) => ({
tabBarIcon: ({ color, size }) => {
const iconName = route.name === 'Home' ? 'home' : 'person';
return ;
},
tabBarActiveTintColor: 'tomato',
tabBarInactiveTintColor: 'gray'
})}>


import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();

function HomeStack() {
return (




);
}




Common Mistakes

  • Forgetting NavigationContainer: Always wrap the top-level navigator so navigation state works correctly.
  • Using duplicate screen names: Each route name should be unique within the navigator.
  • Mixing tab and stack logic incorrectly: Nest stacks inside tabs when a tab needs multiple screens.
  • Not installing peer dependencies: React Navigation requires supporting packages; check setup docs carefully.

Best Practices

  • Use tabs only for top-level sections of your app.
  • Keep tab labels short and clear.
  • Add icons to improve recognition and usability.
  • Avoid placing too many tabs; four or five is usually enough.
  • Use nested navigators for complex flows instead of overcrowding one screen.

Practice Exercises

  • Create a bottom tab navigator with three tabs: Home, Search, and Profile.
  • Add icons and active/inactive colors to each tab.
  • Nest a stack navigator inside the Home tab and add a Details screen.

Mini Project / Task

Build a simple shopping app navigation layout with four tabs: Home, Categories, Cart, and Account. Make the Home tab contain a nested stack with a product details screen.

Challenge (Optional)

Customize the tab bar so one tab shows a badge count, such as the number of items currently in the cart, and update that badge dynamically from component state.

Drawer Navigator

Drawer Navigator is a navigation pattern in React Native that shows a side panel, usually sliding in from the left, containing links to major screens in an app. It exists to help users move quickly between top-level areas such as Home, Profile, Settings, Orders, or Help without placing too many buttons on the main screen. In real mobile apps, drawer menus are common in dashboards, admin apps, e-commerce apps, learning platforms, and productivity tools where users need fast access to several core sections. In React Native, drawer navigation is commonly built with React Navigation using @react-navigation/drawer. The drawer can contain simple screen links, icons, custom content, user profile details, and logout actions. It can open by swiping from the edge or by tapping a menu button. The main idea is that the drawer usually manages top-level app navigation, while stack navigation handles page-to-page transitions inside each section. React Navigation supports several useful drawer features: default drawer items, custom drawer content, drawer position, initial route, screen options, gestures, and nested navigators. This makes it flexible enough for both simple beginner apps and larger production apps.

Step-by-Step Explanation

To use Drawer Navigator, first install React Navigation and the drawer package. Then create a drawer navigator with createDrawerNavigator(). Wrap your app with NavigationContainer, define screens with Drawer.Screen, and pass components for each route. The basic syntax is simple: create the navigator object, render Drawer.Navigator, then list each screen. You can configure titles, icons, swipe behavior, header visibility, and custom styles with screenOptions. If you want a menu button in the header, use the navigation prop and call navigation.openDrawer() or navigation.toggleDrawer(). Beginners should understand that a drawer screen is not just a button; it is a route managed by the navigation library. Another important concept is nesting. For example, the drawer might contain a Home stack, a Shop stack, and a Settings stack. This pattern keeps top-level navigation clean while still allowing deeper navigation inside each section. Custom drawer content is also common. Instead of showing only default route names, you can render a custom component with profile information, theme toggles, or a sign-out button. This is useful in real apps where the drawer acts like a small control panel.

Comprehensive Code Examples

import * as React from 'react';
import { View, Text, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';

function HomeScreen({ navigation }) {
return (

Home Screen
import * as React from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';

function OrdersScreen() { return Orders; }
function ProfileScreen() { return Profile; }

const Drawer = createDrawerNavigator();

export default function App() {
return (

screenOptions={{ drawerPosition: 'left', headerShown: true }}>




);
}
import * as React from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator, DrawerContentScrollView, DrawerItemList, DrawerItem } from '@react-navigation/drawer';

function Dashboard() { return Dashboard; }
function Help() { return Help; }

function CustomDrawerContent(props) {
return (

Welcome, Alex

console.log('Logout')} />

);
}

const Drawer = createDrawerNavigator();

export default function App() {
return (

}>




);
}

Common Mistakes

  • Forgetting to wrap the navigator in NavigationContainer. Fix: place your drawer inside a single top-level navigation container.

  • Using drawer navigation for every screen, including tiny detail pages. Fix: use the drawer for top-level sections and stacks for inner screens.

  • Trying to call openDrawer() from a component that does not receive navigation props. Fix: pass navigation correctly or use hooks such as useNavigation.

Best Practices

  • Keep drawer items limited to major app areas so the menu stays clear and useful.

  • Use meaningful labels and icons so users can scan the menu quickly.

  • Combine drawer navigation with stack navigators for scalable app structure.

  • Add custom drawer content for branding, profile info, or account actions.

Practice Exercises

  • Create a drawer with three screens: Home, Profile, and Settings.

  • Add a button on the Home screen that opens the drawer menu.

  • Customize the drawer to include a welcome message above the menu items.

Mini Project / Task

Build a small student portal app with a drawer that links to Dashboard, Courses, Assignments, and Account screens. Add a custom drawer header with the student name and a logout button.

Challenge (Optional)

Create a drawer-based app where one drawer item opens a nested stack navigator, allowing users to move from a list screen to a details screen while still keeping the drawer available from the header.

Passing Params Between Screens

Passing params between screens in React Native usually happens when using a navigation library such as React Navigation. Params are small pieces of data sent from one screen to another during navigation. They exist so one screen can give context to the next screen, such as a product ID, a user name, a selected category, or a filter value. In real apps, this is extremely common. For example, a shopping app sends a product ID from a product list screen to a product details screen. A messaging app sends a conversation ID from an inbox screen to a chat screen. A profile app may send a user object or username from a search result screen to a profile details screen.

The basic idea is simple: when calling navigation.navigate(), you include a second argument containing the params object. The destination screen then reads those values from route.params. Params can be required or optional. You can pass simple values like strings and numbers, or structured values like objects and arrays. However, params should stay lightweight. They are best used for navigation context, not for storing your entire app state.

Another important concept is updating params after a screen has already loaded. React Navigation provides methods like navigation.setParams() for modifying route params. You may also provide default values in case a param is missing. This helps avoid crashes when users enter a screen from a different path. Understanding params improves app flow because screens become reusable and dynamic instead of hard-coded.

Step-by-Step Explanation

First, create screens inside a navigator such as a stack navigator. Second, from the current screen, call navigation.navigate('ScreenName', { key: value }). Third, in the target screen, access the data using route.params. Fourth, safely handle missing params using optional chaining like route.params?.name or fallback values.

The syntax has two main parts. The sender uses the navigation object. The receiver uses the route object. In function components, both are usually available as props when the component is registered as a screen.

navigation.navigate('Profile', { username: 'Aisha', age: 25 })
function ProfileScreen({ route }) {
const username = route.params?.username;
const age = route.params?.age;
}

Comprehensive Code Examples

Basic example
function HomeScreen({ navigation }) {
return (
title="Go to Details"
onPress={() => navigation.navigate('Details', { message: 'Hello from Home' })}
/>
);
}

function DetailsScreen({ route }) {
return {route.params?.message};
}
Real-world example
function ProductListScreen({ navigation }) {
const product = { id: 101, name: 'Wireless Headphones', price: 79.99 };

return (
title="View Product"
onPress={() => navigation.navigate('ProductDetails', { product })}
/>
);
}

function ProductDetailsScreen({ route }) {
const product = route.params?.product;

return (
<>
{product?.name}
${product?.price}

);
}
Advanced usage
function EditProfileScreen({ navigation, route }) {
const currentName = route.params?.name ?? 'Guest';

const saveProfile = () => {
navigation.navigate('Profile', { name: 'Updated Name', refreshedAt: Date.now() });
};

return (
<>
Current: {currentName}

Common Mistakes

  • Forgetting to pass params: Trying to read route.params.name when nothing was sent. Fix it with route.params?.name and default values.
  • Passing huge objects: Sending entire datasets through navigation. Fix it by passing only IDs or small relevant values.
  • Using the wrong screen name: Calling navigate() with a name not registered in the navigator. Fix it by matching the exact route name.
  • Confusing navigation and route: Reading params from navigation.params. Fix it by using route.params.

Best Practices

  • Pass minimal data: Prefer IDs, slugs, or lightweight objects.
  • Use fallback values: Prevent crashes with optional chaining and defaults.
  • Keep params serializable: Avoid functions or complex class instances when possible.
  • Name params clearly: Use descriptive keys like productId instead of vague names like data.

Practice Exercises

  • Create a Home screen that passes a user name to a Welcome screen and displays it.
  • Build a list screen that passes a selected item ID to a details screen.
  • Create a form screen that sends a city name and country to a summary screen.

Mini Project / Task

Build a simple recipe app with a Recipe List screen and a Recipe Details screen. When a user taps a recipe, pass the recipe name, cooking time, and difficulty level to the details screen and display them.

Challenge (Optional)

Create a multi-screen flow where one screen sends params to a second screen, the second screen edits the data, and a third screen displays the updated values safely with fallback handling.

Nested Navigators

Nested navigators let you place one navigator inside another, such as a stack navigator inside a tab navigator, or tabs inside a drawer. This exists because real mobile apps rarely use a single navigation style. For example, a shopping app may use bottom tabs for Home, Search, and Account, while the Home tab itself uses a stack to move from a product list to product details. In React Native, nested navigators are commonly built with React Navigation so each part of the app can manage its own screen flow while still fitting into a larger app structure.

The most common combinations are stack inside tabs, tabs inside stack, and drawer with stacks or tabs. A stack is useful for drill-down flows like list to details. Tabs are useful for top-level app areas users switch between often. A drawer is useful for global sections such as Settings or Help. The main idea is that every navigator controls its own history. That means a stack inside one tab remembers its own screen history even when the user switches to another tab.

Step-by-Step Explanation

First, install React Navigation and the navigator packages you need, such as native stack and bottom tabs. Next, create simple screen components. Then create smaller navigators first, such as a Home stack. After that, place those navigators inside a parent navigator, such as bottom tabs. Finally, wrap the root navigator with NavigationContainer.

When nesting, think of each navigator as a screen of the parent navigator. For example, in a tab navigator, one tab can render HomeStack instead of a single screen. Navigation works in layers. If you call navigation.navigate('Details') inside the Home stack, it moves within that stack. If you want to move to a different parent route, you usually navigate to the parent screen name, sometimes with nested params.

Comprehensive Code Examples

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 { View, Text, Button } from 'react-native';

const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();

function HomeScreen({ navigation }) {
  return (
    
      Home
      

Real-world example: an account area with tabs and a nested orders stack.

function OrdersScreen({ navigation }) {
  return 

Advanced usage: navigate into a nested screen from the parent.

navigation.navigate('HomeTab', {
  screen: 'Details',
  params: { from: 'notification' }
});

Common Mistakes

  • Using duplicate screen names: Repeating names like Details in multiple places can confuse navigation. Use clear names or know which navigator owns the route.
  • Navigating at the wrong level: Trying to open a child screen directly from a parent without nested params fails. Fix it with navigation.navigate('Parent', { screen: 'Child' }).
  • Wrapping multiple containers: Only one root NavigationContainer is usually needed. Extra containers break shared navigation state.
  • Ignoring separate histories: Each nested navigator has its own back stack. Test tab switching and back behavior carefully.

Best Practices

  • Use tabs, stacks, and drawers only where they match user behavior.
  • Keep navigator files modular, such as HomeStack.js and AccountTabs.js.
  • Use meaningful route names like ProductDetails instead of generic names when apps grow.
  • Configure headers intentionally so parent and child headers do not conflict.
  • Document your navigation tree for team clarity.

Practice Exercises

  • Create a bottom tab navigator with two tabs: Feed and Profile. Put a stack inside Feed with FeedList and FeedDetails screens.
  • Add a button on FeedList that opens FeedDetails using stack navigation.
  • From Profile, navigate to FeedDetails through the parent tab using nested navigation params.

Mini Project / Task

Build a small shopping app navigation flow with bottom tabs for Home, Cart, and Account. Inside Home, add a stack with ProductList and ProductDetails. Make sure switching tabs preserves the Home stack history.

Challenge (Optional)

Create a drawer navigator as the app root, place bottom tabs inside one drawer item, and then add a stack inside the Home tab. Test how back behavior works across all three levels.

Handling User Input


Handling user input is a fundamental aspect of building interactive mobile applications. In React Native, this involves capturing various forms of user interaction, such as text entry, button presses, touch gestures, and more. Without the ability to receive and process user input, an application would be static and unresponsive, failing to meet the basic expectations of modern mobile users. React Native provides a rich set of components and APIs specifically designed to facilitate robust and intuitive input handling across both iOS and Android platforms, ensuring a consistent user experience.


The importance of effective input handling extends beyond mere functionality. It directly impacts the usability and accessibility of your application. A well-designed input system can make an app feel natural and easy to navigate, while a poorly implemented one can lead to frustration and abandonment. For instance, in a social media app, users need to type posts, tap on 'like' buttons, and scroll through feeds. In an e-commerce app, they input search queries, select product options, and confirm purchases. Each of these interactions relies heavily on React Native's capabilities for handling user input. Understanding how to correctly implement and manage these inputs is crucial for any React Native developer.


Core Concepts & Sub-types

React Native offers several core components and concepts for handling different types of user input:


  • TextInput: This is the primary component for allowing users to enter text. It’s highly customizable, supporting single-line and multi-line input, secure text entry (for passwords), numeric keypads, and more. It provides various props to control its behavior and appearance, and events to capture changes.

  • Touchable Components (e.g., TouchableOpacity, TouchableWithoutFeedback, TouchableHighlight, Pressable): These components are used to make views respond to touch events. They are essential for creating interactive buttons, links, or any tappable UI element. TouchableOpacity is commonly used for buttons, providing a visual feedback (opacity reduction) when pressed. Pressable is a newer and more flexible API that provides more fine-grained control over various press states and interactions.

  • Switch: A simple toggle switch component, similar to an iOS switch or Android toggle button. It's used for binary choices, such as enabling/disabling a setting.

  • Slider: Allows users to select a value from a continuous range by moving a thumb along a track. Useful for volume controls, brightness settings, or progress indicators.

  • Keyboard Handling: React Native provides APIs like KeyboardAvoidingView and the Keyboard API to manage the on-screen keyboard. This is crucial for ensuring that input fields are not obscured by the keyboard and that the UI adjusts appropriately.

  • Gestures (PanResponder): For more complex touch interactions like swiping, pinching, or dragging, React Native offers the PanResponder system. This API allows you to track multi-touch gestures and implement custom gesture recognition.

Step-by-Step Explanation


Let's break down the usage of some common input components:


  • TextInput:
     1. Import TextInput from 'react-native'.
     2. Render the component.
     3. Use the value prop to control the text displayed (usually from state).
     4. Use the onChangeText prop to update the state whenever the text changes. This prop receives the new text as an argument.
     5. Add other props like placeholder, keyboardType, secureTextEntry, etc., for customization.

  • TouchableOpacity:
     1. Import TouchableOpacity from 'react-native'.
     2. Wrap the content you want to make tappable (e.g., , ) inside .
     3. Use the onPress prop to define a function that will be executed when the component is pressed.

  • Switch:
     1. Import Switch from 'react-native'.
     2. Render the component.
     3. Use the value prop to control its checked state (boolean, usually from state).
     4. Use the onValueChange prop to update the state when the switch is toggled. This prop receives a boolean indicating the new value.

Comprehensive Code Examples


Basic example

import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, Switch, StyleSheet } from 'react-native';

const BasicInputExample = () => {
const [name, setName] = useState('');
const [isDarkMode, setIsDarkMode] = useState(false);

return (

Enter your name:
style={styles.input}
placeholder="John Doe"
value={name}
onChangeText={setName}
/>
Hello, {name || 'Guest'}!

alert(`Hello, ${name || 'Guest'}!`)}>
Say Hello



Dark Mode:
value={isDarkMode}
onValueChange={setIsDarkMode}
trackColor={{ false: "#767577", true: "#81b0ff" }}
thumbColor={isDarkMode ? "#f5dd4b" : "#f4f3f4"}
/>

Dark Mode is {isDarkMode ? 'On' : 'Off'}

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
backgroundColor: '#f0f0f0',
},
label: {
fontSize: 18,
marginBottom: 5,
marginTop: 15,
fontWeight: 'bold',
},
input: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
width: '80%',
paddingHorizontal: 10,
marginBottom: 10,
borderRadius: 5,
backgroundColor: 'white',
},
button: {
backgroundColor: '#007bff',
padding: 10,
borderRadius: 5,
marginTop: 10,
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
resultText: {
fontSize: 20,
marginTop: 10,
color: '#333',
},
switchContainer: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 20,
},
});

export default BasicInputExample;

Real-world example

import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, Alert, StyleSheet } from 'react-native';

const LoginForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');

const handleLogin = () => {
if (email === '[email protected]' && password === 'password123') {
Alert.alert('Login Successful', `Welcome back, ${email}!`);
// Navigate to home screen or perform other actions
} else {
Alert.alert('Login Failed', 'Invalid email or password.');
}
};

return (

User Login

style={styles.input}
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
value={email}
onChangeText={setEmail}
/>

style={styles.input}
placeholder="Password"
secureTextEntry
value={password}
onChangeText={setPassword}
/>


Log In


Alert.alert('Forgot Password', 'Please contact support.')}>
Forgot Password?


);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
backgroundColor: '#e0f7fa',
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 30,
color: '#00796b',
},
input: {
height: 50,
borderColor: '#00bcd4',
borderWidth: 1,
width: '90%',
paddingHorizontal: 15,
marginBottom: 15,
borderRadius: 8,
backgroundColor: 'white',
fontSize: 16,
},
button: {
backgroundColor: '#00bcd4',
paddingVertical: 12,
paddingHorizontal: 30,
borderRadius: 8,
marginTop: 10,
width: '90%',
alignItems: 'center',
},
buttonText: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
},
forgotPasswordText: {
marginTop: 20,
color: '#00796b',
fontSize: 14,
},
});

export default LoginForm;

Advanced usage

import React, { useState, useRef } from 'react';
import { View, Text, TextInput, StyleSheet, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';

const AdvancedForm = () => {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [address, setAddress] = useState('');

const lastNameRef = useRef(null);
const emailRef = useRef(null);
const phoneRef = useRef(null);
const addressRef = useRef(null);

return (
style={styles.keyboardAvoidingView}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>

User Registration

style={styles.input}
placeholder="First Name"
value={firstName}
onChangeText={setFirstName}
returnKeyType="next"
onSubmitEditing={() => lastNameRef.current.focus()}
/>

ref={lastNameRef}
style={styles.input}
placeholder="Last Name"
value={lastName}
onChangeText={setLastName}
returnKeyType="next"
onSubmitEditing={() => emailRef.current.focus()}
/>

ref={emailRef}
style={styles.input}
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
value={email}
onChangeText={setEmail}
returnKeyType="next"
onSubmitEditing={() => phoneRef.current.focus()}
/>

ref={phoneRef}
style={styles.input}
placeholder="Phone Number"
keyboardType="phone-pad"
value={phone}
onChangeText={setPhone}
returnKeyType="next"
onSubmitEditing={() => addressRef.current.focus()}
/>

ref={addressRef}
style={styles.input}
placeholder="Address"
value={address}
onChangeText={setAddress}
multiline
numberOfLines={3}
returnKeyType="done"
/>


Summary: {firstName} {lastName}, {email}, {phone}, {address}



);
};

const styles = StyleSheet.create({
keyboardAvoidingView: {
flex: 1,
},
scrollViewContent: {
flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
backgroundColor: '#fff3e0',
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 30,
color: '#ff9800',
},
input: {
height: 50,
borderColor: '#ffb74d',
borderWidth: 1,
width: '90%',
paddingHorizontal: 15,
marginBottom: 15,
borderRadius: 8,
backgroundColor: 'white',
fontSize: 16,
},
summaryText: {
marginTop: 20,
fontSize: 16,
color: '#e65100',
textAlign: 'center',
},
});

export default AdvancedForm;

Common Mistakes


  • Forgetting to use onChangeText with TextInput: A common mistake is to set the value prop of a TextInput but forget to provide an onChangeText handler to update the state. This results in an uneditable input field, as the value never changes. Always pair value with onChangeText for controlled components.
     Fix: Ensure your TextInput has both value={yourStateVariable} and onChangeText={newText => setYourStateVariable(newText)}.

  • Not handling keyboard visibility: On mobile, the on-screen keyboard can often obscure input fields, making it difficult for users to see what they are typing. Ignoring this leads to a poor user experience.
     Fix: Use KeyboardAvoidingView to automatically adjust the view's position when the keyboard appears. For more complex scenarios, use the Keyboard API to listen for keyboard events and manually adjust layout.

  • Using View for tappable elements instead of Touchable* components: While you can attach onPress to a View, it doesn't provide visual feedback (like opacity change or highlight) by default, making it unclear to the user that the element is tappable. This harms usability.
     Fix: Always use TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, or Pressable for interactive elements that respond to touches. They provide built-in feedback and are designed for this purpose.

Best Practices


  • Use controlled components: For TextInput, Switch, and Slider, always manage their values with React state. This gives you full control over their behavior and makes debugging easier.

  • Provide clear visual feedback for interactions: Use TouchableOpacity or Pressable for buttons and tappable areas to give users immediate feedback that their touch has been registered.

  • Optimize keyboard experience: Implement KeyboardAvoidingView or use the Keyboard API to prevent the keyboard from obscuring input fields. Use returnKeyType and onSubmitEditing on TextInput to improve navigation between fields.

  • Validate user input: Before submitting data, always validate user input (e.g., email format, password strength, numeric values). Provide clear error messages to guide the user.

  • Consider accessibility: Use accessibility props like accessibilityLabel, accessibilityHint, and role to make your input components usable for users with disabilities.

Practice Exercises


  • Exercise 1 (Beginner): Create a screen with a TextInput that takes a user's favorite color. Display the entered color in a Text component below the input, updating in real-time.

  • Exercise 2 (Intermediate): Build a simple counter app. It should have a Text component displaying a number (starting at 0), and two TouchableOpacity buttons: one to increment the number and one to decrement it.

  • Exercise 3 (Intermediate): Design a screen with a Switch component labeled "Enable Notifications". When the switch is toggled, display an Alert indicating whether notifications are now enabled or disabled.

Mini Project / Task


Create a simple profile editor screen. It should include:
 - A TextInput for 'Name'.
 - A TextInput for 'Bio' (multiline).
 - A Switch for 'Public Profile'.
 - A TouchableOpacity button labeled 'Save Changes'.
When the 'Save Changes' button is pressed, display an Alert showing all the entered profile information.


Challenge (Optional)


Enhance the profile editor from the Mini Project. Implement form validation:
 - 'Name' cannot be empty.
 - 'Bio' must be at least 10 characters long.
If validation fails, prevent the 'Save Changes' alert and instead display specific error messages below the respective input fields. Use the KeyboardAvoidingView to ensure all inputs are visible when the keyboard is active.

Forms and Validation



Forms are fundamental components of almost any interactive application, allowing users to input data. In React Native, forms are built using various UI components like TextInput, Switch, Picker (or community packages for dropdowns), and Button for submission. Validation, on the other hand, is the process of ensuring that the data entered by the user meets specific criteria before it's processed or stored. This is crucial for maintaining data integrity, providing a good user experience by giving immediate feedback, and preventing errors or malicious input. Real-world applications like e-commerce checkout pages, user registration/login screens, settings pages, and data entry forms heavily rely on robust form handling and validation.

While React Native doesn't provide a built-in 'Form' component like HTML, you manage form state using React's local state, often with the useState hook. Validation can be implemented manually with conditional logic or by using third-party libraries. The core idea is to capture user input, store it in the component's state, and then apply rules to that state to determine if the input is valid. When validation fails, appropriate error messages should be displayed to guide the user.

Step-by-Step Explanation


1. State Management: For each input field, create a corresponding state variable using useState. This state will hold the current value of the input.
const [email, setEmail] = useState('');
2. Input Component: Use TextInput components for text-based inputs. Link its value prop to your state variable and its onChangeText prop to update the state.

3. Validation Logic: Create functions or inline logic to check if input values meet criteria (e.g., email format, minimum length, non-empty). This can be triggered on input change, on blur, or on form submission.
4. Error State: Maintain separate state variables to store validation error messages for each field.
const [emailError, setEmailError] = useState('');
5. Displaying Errors: Conditionally render Text components with error messages near the respective input fields.
{emailError ? {emailError} : null}
6. Form Submission: Create a submission handler function. Inside this function, perform all necessary validations. If all inputs are valid, proceed with the form submission (e.g., send data to an API). If not, update error states and prevent submission.

Comprehensive Code Examples


Basic Example: Simple Login Form

import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet, Alert } from 'react-native';

const LoginForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [emailError, setEmailError] = useState('');
const [passwordError, setPasswordError] = useState('');

const validateForm = () => {
let isValid = true;
setEmailError('');
setPasswordError('');

if (!email.includes('@')) {
setEmailError('Please enter a valid email address.');
isValid = false;
}
if (password.length < 6) {
setPasswordError('Password must be at least 6 characters long.');
isValid = false;
}
return isValid;
};

const handleSubmit = () => {
if (validateForm()) {
Alert.alert('Login Success', `Email: ${email}, Password: ${password}`);
// In a real app, you'd send this data to an API
} else {
Alert.alert('Validation Error', 'Please correct the errors in the form.');
}
};

return (

Login
style={styles.input}
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
value={email}
onChangeText={setEmail}
/>
{emailError ? {emailError} : null}

style={styles.input}
placeholder="Password"
secureTextEntry
value={password}
onChangeText={setPassword}
/>
{passwordError ? {passwordError} : null}



Real-world Example: Using Formik and Yup for Advanced Validation

For complex forms, managing state and validation manually can become tedious. Libraries like Formik and Yup simplify this greatly.
import React from 'react';
import { View, Text, TextInput, Button, StyleSheet, Alert } from 'react-native';
import { Formik } from 'formik';
import * as Yup from 'yup';

const SignupSchema = Yup.object().shape({
username: Yup.string()
.min(3, 'Too Short!')
.max(50, 'Too Long!')
.required('Required'),
email: Yup.string().email('Invalid email').required('Required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Required'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password'), null], 'Passwords must match')
.required('Required'),
});

const SignupForm = () => (
initialValues={{ username: '', email: '', password: '', confirmPassword: '' }}
validationSchema={SignupSchema}
onSubmit={values => {
Alert.alert('Signup Success', JSON.stringify(values, null, 2));
// Submit to API
}}
>
{({ handleChange, handleBlur, handleSubmit, values, errors, touched }) => (

Sign Up
style={styles.input}
placeholder="Username"
onChangeText={handleChange('username')}
onBlur={handleBlur('username')}
value={values.username}
/>
{errors.username && touched.username ? (
{errors.username}
) : null}

style={styles.input}
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
onChangeText={handleChange('email')}
onBlur={handleBlur('email')}
value={values.email}
/>
{errors.email && touched.email ? (
{errors.email}
) : null}

style={styles.input}
placeholder="Password"
secureTextEntry
onChangeText={handleChange('password')}
onBlur={handleBlur('password')}
value={values.password}
/>
{errors.password && touched.password ? (
{errors.password}
) : null}

style={styles.input}
placeholder="Confirm Password"
secureTextEntry
onChangeText={handleChange('confirmPassword')}
onBlur={handleBlur('confirmPassword')}
value={values.confirmPassword}
/>
{errors.confirmPassword && touched.confirmPassword ? (
{errors.confirmPassword}
) : null}



Advanced Usage: Custom Input Component with Internal Validation

Encapsulating input and validation logic into reusable components.
import React, { useState } from 'react';
import { View, Text, TextInput, StyleSheet } from 'react-native';

const ValidatedInput = ({ label, value, onChangeText, validationFn, errorMessage, ...props }) => {
const [error, setError] = useState('');

const handleBlur = () => {
if (validationFn && !validationFn(value)) {
setError(errorMessage);
} else {
setError('');
}
};

const handleChange = (text) => {
onChangeText(text);
// Clear error on change if it becomes valid, or keep it if still invalid
if (validationFn && validationFn(text) && error) {
setError('');
} else if (validationFn && !validationFn(text) && !error && text.length > 0) {
// Optionally show error as user types
setError(errorMessage);
} else if (text.length === 0) {
setError(''); // Clear error if input is empty
}
};

return (

{label}
style={[styles.input, error ? styles.inputError : {}]}
value={value}
onChangeText={handleChange}
onBlur={handleBlur}
{...props}
/>
{error ? {error} : null}

);
};

const styles = StyleSheet.create({
inputGroup: {
marginBottom: 15,
},
label: {
fontSize: 16,
marginBottom: 5,
fontWeight: '500',
},
input: {
height: 45,
borderColor: '#ccc',
borderWidth: 1,
borderRadius: 8,
paddingHorizontal: 10,
backgroundColor: '#fff',
},
inputError: {
borderColor: 'red',
borderWidth: 2,
},
errorText: {
color: 'red',
marginTop: 5,
fontSize: 12,
},
});

export default ValidatedInput;


Common Mistakes



  • Not managing input state correctly: Forgetting to set the value prop of TextInput to the state variable or not updating the state with onChangeText leads to uncontrolled components where input doesn't reflect state.
    Fix: Always bind value={stateVariable} and onChangeText={text => setStateVariable(text)}.

  • Over-validating or under-validating: Validating on every keystroke for complex rules can be jarring, while only validating on submit might frustrate users with a long list of errors at once.
    Fix: Implement a mix: simple validations (e.g., required fields) can be checked on blur or change, while complex validations can be done on submit. Libraries like Formik handle this gracefully with touched state.

  • Poor error message display: Error messages that are unclear, hidden, or disappear too quickly create a bad user experience.
    Fix: Ensure error messages are clearly visible, descriptive, and positioned close to the input they relate to. Use distinct styling (e.g., red text) for errors.



Best Practices



  • Use dedicated form libraries for complex forms: For anything beyond the simplest forms, consider libraries like Formik, React Hook Form, or Redux Form. They abstract away much of the boilerplate for state management, validation, and submission.

  • Centralize validation schemas: Use schema validation libraries like Yup or Zod to define your validation rules in a single, reusable place. This makes your validation logic cleaner and easier to maintain.

  • Provide immediate and clear feedback: Show error messages as soon as an input becomes invalid (e.g., on blur) or after a submission attempt. Don't wait until the user clicks submit to show all errors.

  • Disable submit button until form is valid: For critical forms, prevent users from submitting invalid data by disabling the submit button. Re-enable it once all validation passes.

  • Handle keyboard behavior: Use KeyboardAvoidingView or adjust ScrollView to ensure input fields are not hidden by the keyboard. Set appropriate keyboardType and returnKeyType props for better UX.



Practice Exercises



  • Basic Contact Form: Create a simple contact form with fields for Name (required), Email (valid email format), and Message (minimum 10 characters). Implement manual validation and display error messages.

  • Password Strength Indicator: Build a registration form with a password field. As the user types, display a simple password strength indicator (e.g., 'Weak', 'Medium', 'Strong') based on length, presence of numbers, and special characters. Do not use external libraries for this, implement the logic yourself.

  • Dynamic Field Validation: Create a form where a field's validation depends on another. For example, if a 'Shipping Address' checkbox is checked, then 'Street', 'City', and 'Zip Code' fields become required.



Mini Project / Task


Build a user profile editing screen. It should include fields for: First Name (required, min 2 chars), Last Name (required, min 2 chars), Phone Number (optional, but if entered, must be 10 digits), and Bio (optional, max 100 chars). Implement validation for all fields. Display success or error messages upon attempting to save the profile.

Challenge (Optional)


Integrate a third-party form library like Formik with Yup validation into the user profile editing screen from the mini-project. Extend the validation to include an asynchronous check for username availability (simulate an API call with a setTimeout). The form should prevent submission and show an error if the simulated username is 'admin' or 'testuser'.

Keyboard Handling

Keyboard handling in React Native means controlling how your app behaves when the on-screen keyboard appears, changes size, or disappears. This matters because text inputs can easily become hidden behind the keyboard, especially on smaller phones. In real applications such as login screens, chat apps, profile forms, and checkout pages, users expect fields to remain visible and easy to reach while typing. React Native provides several tools for this, including KeyboardAvoidingView, ScrollView with keyboard-related props, the Keyboard API for listening to show and hide events, and input props like returnKeyType and blurOnSubmit. On iOS and Android, keyboard behavior is not identical, so developers must test both platforms. Common patterns include pushing content upward, making forms scroll automatically, dismissing the keyboard when tapping outside, and adjusting layouts for focused inputs. Understanding keyboard handling improves usability, accessibility, and form completion rates because users do not have to fight the interface while entering data.

Step-by-Step Explanation

Start by wrapping form content in KeyboardAvoidingView. Its behavior prop controls how the layout reacts, usually padding on iOS and height or no value on Android. Next, place long forms inside a ScrollView and set keyboardShouldPersistTaps so taps work correctly while the keyboard is open. If you want to dismiss the keyboard when the user taps outside an input, use the Keyboard.dismiss() method with a touchable wrapper. For more control, subscribe to keyboard events such as keyboardDidShow and keyboardDidHide using the Keyboard module. This is useful for showing helper actions, changing spacing, or tracking keyboard visibility in state. For multiple inputs, connect them with refs so the return key moves focus to the next field. Finally, apply platform-specific offsets with keyboardVerticalOffset when headers or navigation bars are present, otherwise the layout may still overlap.

Comprehensive Code Examples

import React from 'react';
import { KeyboardAvoidingView, Platform, TextInput, View, StyleSheet } from 'react-native';

export default function BasicForm() {
return (
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>





);
}

const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 16 },
input: { borderWidth: 1, marginBottom: 12, padding: 12, borderRadius: 8 }
});
import React from 'react';
import { Keyboard, ScrollView, TextInput, TouchableWithoutFeedback } from 'react-native';

export default function RealWorldProfileForm() {
return (







);
}
import React, { useEffect, useRef, useState } from 'react';
import { Keyboard, Text, TextInput, View } from 'react-native';

export default function AdvancedKeyboardState() {
const [visible, setVisible] = useState(false);
const emailRef = useRef(null);
const passwordRef = useRef(null);

useEffect(() => {
const showSub = Keyboard.addListener('keyboardDidShow', () => setVisible(true));
const hideSub = Keyboard.addListener('keyboardDidHide', () => setVisible(false));
return () => { showSub.remove(); hideSub.remove(); };
}, []);

return (

Keyboard: {visible ? 'Open' : 'Closed'}
ref={emailRef}
placeholder="Email"
returnKeyType="next"
onSubmitEditing={() => passwordRef.current?.focus()}
style={{ borderWidth: 1, marginTop: 12, padding: 12 }} />
ref={passwordRef}
placeholder="Password"
secureTextEntry
returnKeyType="done"
style={{ borderWidth: 1, marginTop: 12, padding: 12 }} />

);
}

Common Mistakes

  • Using forms without KeyboardAvoidingView or scrolling, which hides inputs behind the keyboard. Fix: wrap content properly.

  • Forgetting to remove keyboard listeners. Fix: clean up subscriptions in useEffect return functions.

  • Ignoring platform differences. Fix: test iOS and Android and adjust behavior and offsets carefully.

Best Practices

  • Use scrollable layouts for long forms.

  • Set return key behavior to improve input flow.

  • Dismiss the keyboard on outside taps when appropriate.

  • Keep forms simple and leave enough spacing around focused fields.

Practice Exercises

  • Create a login form that stays visible when the keyboard opens.

  • Build a three-field form where the return key moves to the next input.

  • Add tap-outside behavior to dismiss the keyboard on a profile screen.

Mini Project / Task

Build a contact form with name, email, and message fields that scrolls correctly, avoids keyboard overlap, and dismisses the keyboard when the user taps outside the form.

Challenge (Optional)

Create a chat reply bar that stays above the keyboard and shows a small status label only while the keyboard is visible.

Gestures and Touch Events


Gestures and touch events are fundamental to mobile application development, allowing users to interact with an app through physical actions like tapping, swiping, pinching, and long-pressing. In React Native, handling these interactions is crucial for creating intuitive and responsive user interfaces. These events exist because traditional web-based event models (like mouse clicks) are insufficient for the rich interactive landscape of touchscreens. They enable a natural user experience, mimicking how users interact with physical objects. For example, a swipe gesture might navigate between screens, a pinch gesture could zoom into an image, and a long press might reveal a context menu. Real-life applications extensively use gestures: photo galleries for zooming and swiping, social media apps for liking (double-tap) or refreshing (pull-to-refresh), and mapping applications for panning and zooming.

React Native provides several ways to handle touch and gestures, ranging from simple touch events on basic components to more sophisticated gesture recognizers. The core concept is to capture raw touch input from the screen and interpret it into meaningful user actions. This involves tracking the start, move, and end of touches, as well as their coordinates and pressure.

Core Concepts & Sub-types


React Native offers two primary approaches for gesture handling:

1. Responder System: This is React Native's low-level API for managing touch interactions. It allows components to 'become' the responder to a touch event, meaning they take control of the touch from the moment it starts until it ends. It's powerful but can be complex for common gestures.

  • onStartShouldSetResponder: Asks if this component should become the responder on a touch start.

  • onMoveShouldSetResponder: Asks if this component should become the responder on a touch move.

  • onResponderGrant: The component has become the responder.

  • onResponderMove: User is moving their finger, and this component is the responder.

  • onResponderRelease: User lifts their finger, and this component was the responder.

  • onResponderTerminate: Another component or OS has stolen the responder.



2. `react-native-gesture-handler` Library: This is the recommended, higher-level solution for handling complex gestures. It's a declarative API that leverages native platform gesture recognizers, providing better performance and a more robust solution for handling simultaneous gestures, native animations, and more. It offers various gesture handlers like TapGestureHandler, PanGestureHandler, PinchGestureHandler, LongPressGestureHandler, etc.

Step-by-Step Explanation


For modern React Native development, react-native-gesture-handler is the preferred way to manage gestures. Here's how to use it:

1. Installation: First, install the library and its peer dependencies:
npm install react-native-gesture-handler react-native-reanimated
npx pod-install ios

2. Wrapping your App: Ensure your root component is wrapped with GestureHandlerRootView to allow gestures to work correctly across your app.
import { GestureHandlerRootView } from 'react-native-gesture-handler';

function App() {
return (

{/* Your app content here */}

);
}

3. Using a Specific Gesture Handler: Import the desired handler (e.g., TapGestureHandler) and wrap the component you want to make interactive.
4. Handling Events: Attach an onGestureEvent prop to capture continuous gesture updates (e.g., during a pan) and onHandlerStateChange to react to the gesture's state changes (e.g., when it ends or fails). These events receive a NativeEvent object with details like x, y, translationX, velocity, state (UNDETERMINED, BEGAN, ACTIVE, END, FAILED, CANCELLED).

Comprehensive Code Examples


Basic Example: Simple Tap

import React from 'react';
import { View, Text, StyleSheet, Alert } from 'react-native';
import { TapGestureHandler, State } from 'react-native-gesture-handler';

const BasicTapExample = () => {
const onSingleTap = (event) => {
if (event.nativeEvent.state === State.ACTIVE) {
Alert.alert('Single Tap!', 'You tapped the box.');
}
};

return (


Tap Me


);
};

const styles = StyleSheet.create({
box: {
width: 150,
height: 150,
backgroundColor: 'lightblue',
justifyContent: 'center',
alignItems: 'center',
borderRadius: 10,
margin: 20,
},
text: {
fontSize: 18,
fontWeight: 'bold',
},
});

export default BasicTapExample;


Real-world Example: Draggable Component (Pan Gesture)

import React, { useRef } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
useAnimatedGestureHandler,
withSpring,
} from 'react-native-reanimated';

const DraggableBox = () => {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);

const onPanGestureEvent = useAnimatedGestureHandler({
onStart: (event, ctx) => {
ctx.startX = translateX.value;
ctx.startY = translateY.value;
},
onActive: (event, ctx) => {
translateX.value = ctx.startX + event.translationX;
translateY.value = ctx.startY + event.translationY;
},
onEnd: (event) => {
// Optional: snap back or animate to a certain position
// translateX.value = withSpring(0);
// translateY.value = withSpring(0);
},
});

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

return (


Drag Me


);
};

const styles = StyleSheet.create({
draggableBox: {
width: 100,
height: 100,
backgroundColor: 'salmon',
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
margin: 50,
},
text: {
color: 'white',
fontWeight: 'bold',
},
});

export default DraggableBox;


Advanced Usage: Pinch to Zoom (with react-native-reanimated)

import React from 'react';
import { View, Image, StyleSheet } from 'react-native';
import { PinchGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
useAnimatedGestureHandler,
withSpring,
} from 'react-native-reanimated';

const PinchZoomImage = () => {
const scale = useSharedValue(1);
const focalX = useSharedValue(0);
const focalY = useSharedValue(0);

const onPinchGestureEvent = useAnimatedGestureHandler({
onStart: (event, ctx) => {
ctx.startScale = scale.value;
},
onActive: (event, ctx) => {
scale.value = event.scale * ctx.startScale;
focalX.value = event.focalX;
focalY.value = event.focalY;
},
onEnd: () => {
scale.value = withSpring(1); // Snap back to original size
},
});

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

return (


source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
style={[styles.image, animatedStyle]}
resizeMode="contain"
/>


);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
},
image: {
width: 200,
height: 200,
},
});

export default PinchZoomImage;


Common Mistakes


1. Forgetting GestureHandlerRootView: Without wrapping your app with GestureHandlerRootView, gestures will not register, leading to unresponsive components.
Fix: Add ... at the root of your application, typically in App.js.
2. Misunderstanding State in onHandlerStateChange: Many beginners perform actions on every state change, even during BEGAN or ACTIVE, when they only intend the action to happen once the gesture is completed (END or ACTIVE for immediate feedback).
Fix: Always check event.nativeEvent.state === State.ACTIVE for actions that should occur once on completion (like a tap) or State.END for actions after release. Use onGestureEvent for continuous updates during ACTIVE state.
3. Mixing Gesture Handlers with TouchableOpacity/Pressable: Using both TapGestureHandler and a built-in touchable component (like TouchableOpacity) on the same element can lead to conflicting behaviors or double-firing events.
Fix: Stick to TapGestureHandler for tap interactions when using react-native-gesture-handler for consistency and to avoid conflicts.

Best Practices



  • Use react-native-gesture-handler for Complex Gestures: For anything beyond simple onPress, this library offers superior performance, reliability, and a richer API.

  • Combine with react-native-reanimated: For fluid, performant animations driven by gestures, always pair react-native-gesture-handler with react-native-reanimated. This allows animations to run on the native UI thread, avoiding JavaScript thread bottlenecks.

  • Provide Visual Feedback: When a user performs a gesture, provide immediate visual feedback (e.g., changing opacity, scaling, or moving the element) to confirm the interaction.

  • Handle Concurrent Gestures: If multiple gesture handlers could be active simultaneously (e.g., pan and pinch on the same image), use the simultaneousHandlers prop to specify which handlers can run at the same time.

  • Test on Device: Gestures can feel different on a real device compared to a simulator. Always test your gesture implementations on physical hardware.



Practice Exercises


1. Double Tap Counter: Create a View component. When the user double-taps it, increment a counter displayed within the view.
2. Swipe to Delete: Implement a simple list item. When the user swipes left on the item, display a 'Delete' button that wasn't visible before. You don't need to actually delete the item, just show the button.
3. Long Press Alert: Make a button-like component that, when long-pressed for at least 500ms, shows an alert message 'You long-pressed me!'.

Mini Project / Task


Build a simple image viewer app. It should display a single image. Implement a pan gesture to move the image around the screen and a pinch gesture to zoom in and out of the image. The image should snap back to its original size and position when the gestures end.

Challenge (Optional)


Extend the image viewer from the mini-project. Add a double-tap gesture that toggles the image between its original size and a pre-defined zoomed-in state (e.g., 2x zoom). Ensure this works seamlessly with the pan and pinch gestures, meaning you can pan/pinch while zoomed in, and a double-tap will reset or toggle zoom from any state.

Animations with Animated API

The Animated API in React Native is a built-in system for creating smooth, declarative animations in mobile apps. It exists to help developers move, fade, scale, and transform interface elements without manually updating styles frame by frame. In real applications, animations improve usability and feedback: buttons can gently scale on tap, cards can slide into view, loaders can pulse, and modals can fade in naturally. Good animation makes an app feel more responsive and professional.

The core idea is simple: instead of changing style values directly, you connect an animated value to a component style and then drive that value over time. The most common building block is Animated.Value, which stores a number that can represent opacity, position, scale, or rotation. React Native supports several animation methods, including Animated.timing() for time-based motion, Animated.spring() for natural spring effects, and Animated.decay() for momentum-like movement. You can also combine animations with Animated.sequence(), Animated.parallel(), and Animated.loop().

To animate a component, you usually create an animated value with useRef(new Animated.Value(...)).current, wrap the visual element with an Animated.View or similar animated component, and bind the value to a style property. Some properties, such as opacity and transform, are especially animation-friendly. For better performance, React Native lets you use the native driver with useNativeDriver: true when animating supported properties, offloading work from JavaScript to the native side.

Step-by-Step Explanation

Start by importing Animated from react-native. Next, create an animated value, often starting at 0 or 1. Then place your target UI inside an animated component such as Animated.View. Bind the animated value to styles like opacity or transform: [{ scale: animatedValue }]. Finally, trigger an animation with functions like Animated.timing() and call start(). For more complex motion, use interpolation to convert one animated value into another range, such as turning 0..1 into rotation degrees or pixel movement.


Comprehensive Code Examples

Basic example: fade in a box
import React, { useEffect, useRef } from 'react';
import { Animated, View } from 'react-native';

export default function App() {
  const opacity = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.timing(opacity, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }).start();
  }, []);

  return (
    
      
    
  );
}
Real-world example: pressable card scaling effect
import React, { useRef } from 'react';
import { Animated, Pressable, Text } from 'react-native';

export default function App() {
  const scale = useRef(new Animated.Value(1)).current;

  const pressIn = () => {
    Animated.spring(scale, {
      toValue: 0.95,
      useNativeDriver: true,
    }).start();
  };

  const pressOut = () => {
    Animated.spring(scale, {
      toValue: 1,
      friction: 4,
      useNativeDriver: true,
    }).start();
  };

  return (
    
      
        Tap Me
      
    
  );
}
Advanced usage: slide and rotate with interpolation
import React, { useEffect, useRef } from 'react';
import { Animated } from 'react-native';

export default function App() {
  const progress = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.sequence([
      Animated.timing(progress, { toValue: 1, duration: 800, useNativeDriver: true }),
      Animated.timing(progress, { toValue: 0, duration: 800, useNativeDriver: true }),
    ]).start();
  }, []);

  const translateX = progress.interpolate({ inputRange: [0, 1], outputRange: [0, 150] });
  const rotate = progress.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '180deg'] });

  return ;
}

Common Mistakes

  • Recreating animated values on every render: use useRef so the value persists.
  • Forgetting start(): the animation will not run until you start it.
  • Using native driver with unsupported properties: use it mainly for opacity and transforms, or switch to false if needed.

Best Practices

  • Prefer subtle animation that supports usability instead of distracting movement.
  • Use useNativeDriver: true whenever the property supports it for smoother performance.
  • Keep animation logic isolated in small components or hooks for reuse and clarity.
  • Test animation timing on real devices, not only simulators.

Practice Exercises

  • Create a square that fades from invisible to fully visible over 2 seconds when the screen loads.
  • Build a button that grows slightly when pressed and returns to normal when released.
  • Animate a view from left to right using interpolation and Animated.timing().

Mini Project / Task

Build an animated notification banner that slides down from the top, stays visible for 2 seconds, and then slides back up automatically.


Challenge (Optional)

Create a reusable loading component that combines looping opacity and scale animations to produce a pulsing effect.

Reanimated Library Basics

Reanimated is a React Native animation library designed to create smooth, high-performance animations that run closer to the native layer instead of depending only on the JavaScript thread. In mobile apps, animations are everywhere: buttons that scale on press, cards that slide into view, draggable lists, bottom sheets, gesture-driven menus, and loading indicators. Traditional animations can stutter when the JavaScript thread is busy, but Reanimated helps reduce that problem by moving animation work into a more efficient runtime. This is why it is widely used in modern React Native apps that need fluid interactions, especially when combined with gesture handling.

The library revolves around a few core ideas. A shared value stores animated state such as position, opacity, or scale. An animated style reads shared values and maps them to visual properties. Animation helpers such as withTiming, withSpring, and withRepeat define how values change over time. withTiming is useful for controlled duration-based transitions, while withSpring creates natural motion with bounce and velocity. Reanimated also supports derived values, reactions, interpolation, and gesture-driven updates for advanced interactions.

Step-by-Step Explanation

To use Reanimated, first install and configure the library in your React Native project, including Babel plugin setup if required by your version. In code, import useSharedValue, useAnimatedStyle, and animation helpers. Create a shared value like const offset = useSharedValue(0). This value does not directly render UI. Next, create an animated style with useAnimatedStyle and return an object that uses the shared value, such as a transform or opacity. Then apply that animated style to an Animated.View. When the shared value changes, the component updates smoothly. To animate a change, assign offset.value = withTiming(100) or withSpring(100). The syntax is beginner-friendly once you remember the pattern: create shared value, connect it to animated style, then update the value through an animation function.

Comprehensive Code Examples

import React from 'react';
import { View, Button } from 'react-native';
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';

export default function BasicFade() {
const opacity = useSharedValue(0.2);

const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
}));

return (


import React from 'react';
import { Pressable, Text } from 'react-native';
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';

export default function PressCard() {
const scale = useSharedValue(1);

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

return (
onPressIn={() => { scale.value = withSpring(0.95); }}
onPressOut={() => { scale.value = withSpring(1); }}
>

Tap Me


);
}
import React from 'react';
import { Button } from 'react-native';
import Animated, { useSharedValue, useAnimatedStyle, withRepeat, withTiming, interpolate } from 'react-native-reanimated';

export default function AdvancedPulse() {
const progress = useSharedValue(0);

const style = useAnimatedStyle(() => ({
opacity: interpolate(progress.value, [0, 1], [0.4, 1]),
transform: [{ scale: interpolate(progress.value, [0, 1], [1, 1.2]) }],
}));

return (
<>

Common Mistakes

  • Using regular state instead of shared values: Use useSharedValue for animated data so Reanimated can update efficiently.
  • Forgetting Animated.View: Animated styles must be applied to Animated.View or another animated component, not a plain View.
  • Mutating shared values incorrectly: Update with value, such as scale.value = withSpring(1), not scale = 1.
  • Skipping library setup: If Babel or installation steps are incomplete, animations may fail or behave unexpectedly.

Best Practices

  • Use withSpring for touch interactions and natural motion.
  • Use withTiming for predictable fades, slides, and progress changes.
  • Keep animated logic focused and readable by separating shared values and animated styles clearly.
  • Prefer transform properties like translateX and scale for better performance.
  • Test animations on real devices to verify smoothness and responsiveness.

Practice Exercises

  • Create a box that moves 150 pixels to the right when a button is pressed.
  • Build a circle that fades in and fades out using withTiming.
  • Make a card that shrinks slightly on press and returns to normal on release using withSpring.

Mini Project / Task

Build an animated notification banner that slides down from the top, stays visible briefly, and then slides back up when dismissed.

Challenge (Optional)

Create a pulsing call-to-action button that continuously scales and changes opacity, then stops the animation when the user taps it.

Fetching Data with Fetch API


The Fetch API provides a modern, powerful, and flexible way to make network requests in web and React Native applications. It's a promise-based mechanism for programmatically fetching resources across the network, offering a more robust and versatile alternative to older methods like XMLHttpRequest. In React Native, fetching data is a cornerstone of almost any dynamic application, as mobile apps frequently need to retrieve information from remote servers – whether it's user profiles, product listings, news feeds, or configuration data. The Fetch API allows developers to interact with RESTful APIs, GraphQL endpoints, or any other HTTP-based service, making it indispensable for building interactive and data-driven mobile experiences. Its promise-based nature simplifies asynchronous operations, leading to cleaner and more readable code compared to callback-based approaches. This API is natively supported in React Native, meaning you don't need to install any additional libraries to start making network requests.

The core concept behind the Fetch API is the global fetch() method, which takes one mandatory argument (the URL of the resource to fetch) and an optional second argument (an init object) that allows you to customize the request. The fetch() method returns a Promise that resolves to a Response object. This Response object is not the actual JSON data or text content, but rather an object representing the response to the request. To get the actual data, you need to call another method on the Response object, such as .json() or .text(), which also return promises. This two-step promise resolution is crucial for understanding how fetch works. The init object allows you to specify various request options like the HTTP method (GET, POST, PUT, DELETE), headers, body content, and more, giving you fine-grained control over your network requests.

Step-by-Step Explanation


1. Initiate the Fetch Request: Call fetch('your-api-url'). This returns a Promise that resolves with a Response object.
2. Handle the Response: Use .then() to access the Response object. It's good practice to check response.ok (which is true for 2xx status codes) to ensure the request was successful before proceeding. If response.ok is false, you might want to throw an error.
3. Extract Data from Response: The Response object has methods like .json(), .text(), .blob(), etc., to parse the response body. These methods also return Promises. For JSON data, you'll typically use response.json().
4. Process the Data: Use another .then() to handle the parsed data. This is where you'll update your component's state or perform other actions with the fetched information.
5. Error Handling: Always include a .catch() block to handle any network errors or issues that occur during the fetch operation or promise chain.

Comprehensive Code Examples


Basic Example: Fetching Public JSON Data

This example demonstrates fetching a list of posts from a public API and displaying them.
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, ActivityIndicator, StyleSheet } from 'react-native';

const BasicFetchExample = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(json => {
setData(json);
})
.catch(e => {
setError(e);
})
.finally(() => {
setLoading(false);
});
}, []);

if (loading) {
return (


Loading posts...

);
}

if (error) {
return (

Error: {error.message}

);
}

return (

Posts List
data={data}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (

{item.title}
{item.body.substring(0, 100)}...

)}
/>

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
center: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 15,
},
item: {
backgroundColor: '#ffffff',
padding: 15,
borderRadius: 8,
marginBottom: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 1.41,
elevation: 2,
},
itemTitle: {
fontSize: 18,
fontWeight: '600',
marginBottom: 5,
},
});

export default BasicFetchExample;

Real-world Example: Posting Data and Handling Authentication

This example shows how to perform a POST request with a JSON body and custom headers, simulating a login.
import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet, Alert, ActivityIndicator } from 'react-native';

const LoginScreen = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState('');

const handleLogin = async () => {
setLoading(true);
setMessage('');
try {
const response = await fetch('https://reqres.in/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
email: username,
password: password,
}),
});

const data = await response.json();

if (!response.ok) {
// Handle server-side errors (e.g., 400 Bad Request if credentials are wrong)
throw new Error(data.error || `Login failed with status: ${response.status}`);
}

// If login successful, 'data' might contain a token
setMessage(`Login successful! Token: ${data.token}`);
Alert.alert('Success', `Logged in with token: ${data.token}`);
} catch (e) {
setMessage(`Login error: ${e.message}`);
Alert.alert('Error', `Login error: ${e.message}`);
} finally {
setLoading(false);
}
};

return (

User Login
style={styles.input}
placeholder="Email"
value={username}
onChangeText={setUsername}
autoCapitalize="none"
keyboardType="email-address"
/>
style={styles.input}
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
title={loading ? "Logging in..." : "Login"}
onPress={handleLogin}
disabled={loading}
/>
{loading && }
{message ? {message} : null}

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
backgroundColor: '#f5f5f5',
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 30,
color: '#333',
},
input: {
width: '100%',
padding: 15,
marginBottom: 15,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
backgroundColor: '#fff',
fontSize: 16,
},
spinner: {
marginTop: 15,
},
message: {
marginTop: 20,
fontSize: 16,
color: '#555',
textAlign: 'center',
},
});

export default LoginScreen;

Advanced Usage: Aborting Requests

This example demonstrates how to use AbortController to cancel a fetch request, which is useful for preventing memory leaks in components that unmount while a fetch is still in progress.
import React, { useState, useEffect } from 'react';
import { View, Text, Button, ActivityIndicator, StyleSheet } from 'react-native';

const AbortFetchExample = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const fetchData = async () => {
const controller = new AbortController();
const signal = controller.signal;
setLoading(true);
setError(null);
setData(null);

try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1', { signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
setData(json);
} catch (e) {
if (e.name === 'AbortError') {
setError('Fetch aborted by user or component unmount.');
} else {
setError(e.message);
}
} finally {
setLoading(false);
}
return controller; // Return controller to allow external abortion
};

useEffect(() => {
let controller;
const loadData = async () => {
controller = await fetchData(); // Store controller for cleanup
};
loadData();

return () => {
// Abort fetch request if component unmounts
if (controller) {
controller.abort();
}
};
}, []);

return (

Fetch with AbortController

Common Mistakes



  • Forgetting the second .then() for data parsing: Many beginners forget that fetch() returns a promise that resolves to a Response object, not the actual data. You need to call response.json() (or .text(), etc.) which itself returns another promise for the data.
    Fix: Always chain a second .then(data => ...) after response.json() or use await response.json() with async/await.


  • Not handling HTTP error statuses: The fetch API does not reject the promise on HTTP error statuses (like 404 or 500). It only rejects on network errors.
    Fix: Manually check response.ok (or response.status) within the first .then() block and throw an error if it's not true.


  • Incorrectly setting headers or body for POST requests: When sending data, especially JSON, you need to set the Content-Type header to 'application/json' and stringify your JavaScript object using JSON.stringify() for the body.
    Fix: Ensure headers: { 'Content-Type': 'application/json' } and body: JSON.stringify(yourDataObject) are correctly configured for POST, PUT, PATCH requests.



Best Practices



  • Use async/await for cleaner code: While .then() chaining works, async/await makes asynchronous code look and behave more like synchronous code, improving readability and maintainability, especially with multiple asynchronous operations.

  • Centralize API calls: Create a dedicated service or utility file (e.g., api.js) to handle all your API requests. This promotes reusability, makes error handling consistent, and simplifies changing API endpoints.

  • Implement robust error handling: Beyond just .catch(), check response.ok or response.status for HTTP errors. Provide user-friendly feedback in case of failures (e.g., toast messages, error screens).

  • Show loading indicators: Always provide visual feedback (e.g., ActivityIndicator) to the user when data is being fetched to improve the user experience.

  • Manage component unmounts with AbortController or cleanup functions: Prevent setting state on unmounted components by aborting pending fetch requests in useEffect cleanup functions. This avoids warnings and potential memory leaks.

  • Use environment variables for API base URLs: Don't hardcode API endpoints directly in your components. Use react-native-dotenv or similar a solution to manage different API URLs for development, staging, and production environments.


Practice Exercises



  • Exercise 1 (Beginner): Create a new React Native component that fetches a random user from https://randomuser.me/api/ and displays their name, email, and a profile picture. Ensure you handle loading and potential errors.

  • Exercise 2 (Intermediate): Modify the login example. Instead of just logging the token, store it securely using AsyncStorage (or react-native-mmkv if you're feeling adventurous) after successful login. Add a 'Logout' button that clears the stored token.

  • Exercise 3 (Intermediate): Build a component that fetches a list of 5 different cryptocurrencies from https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=5&page=1&sparkline=false. Display their name, current price, and a small indicator of price change (e.g., green for up, red for down).


Mini Project / Task


Build a simple Weather App in React Native. The app should:

  • Have a text input field where a user can type a city name.

  • Have a button to 'Get Weather'.

  • When the button is pressed, use the Fetch API to call a public weather API (e.g., OpenWeatherMap, you'll need a free API key) to get the current weather for the entered city.

  • Display the city name, temperature, and weather description (e.g., 'Cloudy', 'Sunny') on the screen.

  • Show a loading indicator while fetching data and display an error message if the city is not found or if there's a network issue.


Challenge (Optional)


Enhance the Weather App to include a feature that automatically fetches weather based on the device's current GPS location when the app starts. You'll need to use React Native's Geolocation API (or a library like react-native-geolocation-service) to get the latitude and longitude, then use these coordinates to make the weather API call. Remember to handle permissions for location access and gracefully degrade if location services are unavailable or denied. Additionally, implement a mechanism to cache recent weather data for a short period (e.g., 5 minutes) to reduce redundant API calls.

Axios in React Native

Axios is a popular HTTP client used to send network requests from a React Native app to servers, APIs, and cloud services. In mobile apps, you often need to fetch user profiles, submit login forms, load product lists, upload data, or call protected backend endpoints. Axios exists to make these tasks easier than working directly with low-level networking tools because it provides a clean syntax, automatic JSON handling, request configuration, interceptors, timeout support, and better error structure. In real applications, Axios is commonly used for authentication, dashboard data loading, search features, payment-related requests, and syncing app data with a backend.

In React Native, Axios is usually installed with npm install axios and then imported where needed. The main request types are GET for reading data, POST for sending new data, PUT or PATCH for updating data, and DELETE for removing data. Axios can be used directly with full URLs, but in larger apps developers often create a reusable Axios instance with a shared baseURL, headers, and timeout settings. Another important concept is error handling. Axios errors may happen because of network failure, server issues, invalid responses, or request timeout, so every request should be wrapped with try/catch when using async/await.

Step-by-Step Explanation

First, install Axios in your React Native project. Next, import it into a component or service file. Then choose the request method you need. A simple GET request uses axios.get(url). The returned promise resolves to a response object. The actual server data is usually inside response.data. For sending data, use axios.post(url, body). You can also pass a third configuration object containing headers such as authorization tokens. In beginner apps, requests are often placed inside useEffect so data loads when the screen opens. In larger apps, it is better to move API logic into a separate file such as api.js or services/userService.js.

Comprehensive Code Examples

import React, { useEffect, useState } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';

export default function App() {
const [users, setUsers] = useState([]);

useEffect(() => {
const fetchUsers = async () => {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/users');
setUsers(response.data);
} catch (error) {
console.log(error);
}
};

fetchUsers();
}, []);

return (

{users.map(user => (
{user.name}
))}

);
}
import axios from 'axios';

const loginUser = async () => {
try {
const response = await axios.post('https://example.com/login', {
email: '[email protected]',
password: '123456'
});
console.log(response.data.token);
} catch (error) {
console.log(error.response?.data || error.message);
}
};
import axios from 'axios';

const api = axios.create({
baseURL: 'https://example.com/api',
timeout: 5000,
headers: { Authorization: 'Bearer TOKEN' }
});

api.interceptors.response.use(
response => response,
error => {
console.log('API Error:', error.message);
return Promise.reject(error);
}
);

export const getProducts = () => api.get('/products');

Common Mistakes

  • Forgetting to use response.data and trying to render the whole response object. Fix: extract only the data payload.

  • Making requests directly in the component body, causing repeated calls on every render. Fix: use useEffect or event handlers.

  • Ignoring errors and loading state. Fix: track loading, success, and error states in the UI.

Best Practices

  • Create one reusable Axios instance for consistent headers, timeout, and base URL.

  • Keep API calls in service files instead of mixing all networking logic into screen components.

  • Handle tokens securely and attach them through headers or interceptors.

  • Show user-friendly loading and error messages for better mobile experience.

Practice Exercises

  • Create a screen that fetches and displays a list of posts from a public API using axios.get().

  • Build a form that sends name and email to a test endpoint using axios.post().

  • Make a reusable Axios instance with a baseURL and use it in two different requests.

Mini Project / Task

Build a simple user directory app that loads users from an API, displays their names and emails, shows a loading message while fetching, and shows an error message if the request fails.

Challenge (Optional)

Create an authenticated API service using an Axios instance and an interceptor that automatically adds a bearer token to every request and logs the user out when a 401 response is returned.

Loading and Error States


Loading and error states are crucial aspects of building robust and user-friendly mobile applications with React Native. They address the asynchronous nature of fetching data, performing complex computations, or interacting with external services. When an app requests data from an API, for instance, it's not instantaneous. Users might experience delays due to network latency, server response times, or the sheer volume of data being transferred. Without proper visual feedback, users might perceive the application as unresponsive or broken, leading to frustration and a poor user experience.

Loading states provide visual cues, such as spinners, progress bars, or skeleton screens, to inform the user that an operation is in progress. This manages user expectations and prevents them from attempting actions that might interrupt the ongoing process or assuming the app has frozen. Conversely, error states are equally vital. Despite best efforts, things can go wrong: network connectivity might be lost, an API might return an error, or data might be malformed. Simply crashing the app or showing a blank screen is unacceptable.

Error states communicate to the user what went wrong, often suggesting solutions (e.g., 'Check your internet connection') or providing options to retry the operation. This transparency helps users understand the problem and empowers them to take corrective action, or at least understand why the app isn't functioning as expected. Implementing these states effectively significantly enhances the perceived performance and reliability of any React Native application, making it more resilient and enjoyable to use in real-world scenarios, from social media apps fetching feeds to e-commerce platforms processing payments.


When dealing with asynchronous operations in React Native, the core concepts revolve around managing the state of your component to reflect the different phases of an operation: an initial idle state, a loading state, a success state, and an error state. This is typically achieved using React's `useState` hook or a state management library. The component's UI then conditionally renders based on these state variables.

  • Loading State: This state is active when an asynchronous operation (like an API call) is initiated and before it completes. Its purpose is to provide immediate feedback to the user that something is happening. Common UI elements include `ActivityIndicator` (spinner), custom loading components, or 'skeleton screens' which mimic the layout of the content that will eventually load. Implementing a loading state prevents users from thinking the app is frozen and improves perceived performance.
  • Error State: This state is activated when an asynchronous operation fails. Failures can stem from various sources: network issues, server errors, invalid data, or permission problems. An effective error state informs the user about the problem, often with a clear, user-friendly message, and sometimes provides options to retry the operation or contact support. UI elements might include `Text` components displaying error messages, `Button` components for retries, or custom error boundary components for catching rendering errors.
  • Data/Success State: Once the asynchronous operation successfully completes and data is received (or an action is confirmed), the component transitions to this state. The fetched data is then rendered in the UI.

Properly managing these states ensures a smooth and predictable user experience, even when external factors introduce delays or failures.

Step-by-Step Explanation


Implementing loading and error states typically involves the following steps:

1. Initialize State Variables: Use `useState` to declare state variables for `loading` (boolean, initially `false`), `error` (can be `null` or an error object/message, initially `null`), and `data` (initially `null` or an empty array/object).
2. Start Loading: Before initiating an asynchronous operation (e.g., inside `useEffect` or an event handler), set `setLoading(true)` and `setError(null)` to clear any previous errors.
3. Perform Asynchronous Operation: Execute your `fetch` call, `axios` request, or any other promise-based operation.
4. Handle Success: In the `.then()` block (or `try` block with `async/await`), set `setData(result)`, then set `setLoading(false)`.
5. Handle Error: In the `.catch()` block (or `catch` block with `async/await`), set `setError(error.message || 'An unknown error occurred')`, then set `setLoading(false)`.
6. Conditional Rendering: In your component's `return` statement, use conditional logic (`if`, `else if`, `else` or ternary operators) to render different UI elements based on the `loading` and `error` state variables.

Comprehensive Code Examples


Basic example

This example demonstrates fetching data and showing a simple loading spinner or error message.
import React, { useState, useEffect } from 'react';
import { View, Text, ActivityIndicator, StyleSheet, Button } from 'react-native';

const BasicDataFetcher = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
setData(json);
} catch (e) {
setError(e.message || 'Failed to fetch data');
} finally {
setLoading(false);
}
};

useEffect(() => {
fetchData();
}, []);

if (loading) {
return (


Loading data...

);
}

if (error) {
return (

Error: {error}

Real-world example

A custom hook for handling data fetching, suitable for reusable logic across multiple components.
import React, { useState, useEffect, useCallback } from 'react';
import { View, Text, ActivityIndicator, StyleSheet, Button, FlatList } from 'react-native';

// Custom Hook for Data Fetching
const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.status}`);
}
const json = await response.json();
setData(json);
} catch (e) {
setError(e.message || 'An unexpected error occurred.');
} finally {
setLoading(false);
}
}, [url]);

useEffect(() => {
fetchData();
}, [fetchData]);

return { data, loading, error, refetch: fetchData };
};

const PostListScreen = () => {
const { data: posts, loading, error, refetch } = useFetch('https://jsonplaceholder.typicode.com/posts');

if (loading) {
return (


Fetching posts...

);
}

if (error) {
return (

Error loading posts: {error}

Advanced usage

Implementing a skeleton loader for a smoother loading experience.
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
import { LinearGradient } from 'expo-linear-gradient'; // Or react-native-linear-gradient

// Skeleton Placeholder Component
const SkeletonPlaceholder = ({ width, height, borderRadius, style }) => {
return (

colors={['#e0e0e0', '#f0f0f0', '#e0e0e0']}
start={[0, 0]}
end={[1, 0]}
style={StyleSheet.absoluteFill}
/>

);
};

const ProductDetailScreen = () => {
const [product, setProduct] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
const fetchProduct = async () => {
setLoading(true);
setError(null);
try {
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 2000));
const response = await fetch('https://fakestoreapi.com/products/1');
if (!response.ok) {
throw new Error(`Failed to fetch product: ${response.status}`);
}
const json = await response.json();
setProduct(json);
} catch (e) {
setError(e.message || 'Could not load product details.');
} finally {
setLoading(false);
}
};
fetchProduct();
}, []);

if (error) {
return (

Error: {error}

);
}

return (

{loading ? (









) : (
product ? (

{product.title}
${product.price ? product.price.toFixed(2) : 'N/A'}
{product.description}
Category: {product.category}

) : (
No product data available.
)
)}

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#fff',
},
skeletonContainer: {
alignItems: 'center',
paddingTop: 20,
},
productTitle: {
fontSize: 26,
fontWeight: 'bold',
marginBottom: 10,
},
productPrice: {
fontSize: 22,
color: '#007bff',
marginBottom: 15,
},
productDescription: {
fontSize: 16,
lineHeight: 24,
marginBottom: 15,
},
productCategory: {
fontSize: 14,
color: '#666',
},
errorText: {
color: 'red',
textAlign: 'center',
marginTop: 50,
fontSize: 18,
},
});

export default ProductDetailScreen;

To use `LinearGradient` in the advanced example, you need to install it. If you're using Expo, run `expo install expo-linear-gradient`. If you're using bare React Native, run `npm install react-native-linear-gradient` or `yarn add react-native-linear-gradient` and then `npx react-native link react-native-linear-gradient`.

Common Mistakes


  • Not setting loading/error states immediately: A common mistake is to delay setting `setLoading(true)` or `setError(null)` until after the async call starts, or forgetting to reset `error` before a retry. This can lead to stale UI or incorrect states.
    Fix: Always set `setLoading(true)` and clear previous errors (`setError(null)`) right before initiating an asynchronous operation.
  • Forgetting `finally` block or `setLoading(false)`: If `setLoading(false)` is only in `.then()` and `.catch()`, an unhandled exception or an early return can leave the app stuck in a loading state.
    Fix: Always use a `finally` block with `setLoading(false)` to ensure the loading state is cleared regardless of success or failure.
  • Generic error messages: Displaying 'An error occurred' without context is unhelpful to the user.
    Fix: Try to provide specific error messages, e.g., 'Network unavailable. Please check your connection.' or 'Failed to load user profile. Please try again later.' based on the error type received from the API.
  • Not handling component unmount during async operations: An API call might resolve after a component has unmounted, leading to a warning or error when trying to update state on an unmounted component.
    Fix: Use a cleanup function in `useEffect` or a ref to track if the component is mounted. For example: `let isMounted = true; useEffect(() => { /* async logic */ return () => { isMounted = false; }; }, []);`. Then, inside your async `then/catch`, check `if (isMounted) setState(...)`.

Best Practices


  • Use Custom Hooks for Reusability: Encapsulate fetching logic, including loading and error state management, into custom hooks (like `useFetch` shown above). This promotes code reuse and keeps components cleaner.
  • Provide Meaningful Feedback: Don't just show a spinner. For longer operations, consider progress bars. For errors, provide actionable messages and a retry option. For initial loads, skeleton screens offer a better user experience than blank screens or simple spinners.
  • Debounce/Throttle Refetching: If users can trigger refetches frequently, implement debouncing or throttling to prevent excessive API calls.
  • Implement Error Boundaries: For catching rendering errors (not async errors), use React Error Boundaries (though they are not built into functional components with hooks, you can wrap your functional components with a class-based error boundary or use libraries like `react-error-boundary`).
  • Optimistic UI Updates (for mutations): For actions like 'liking' a post, update the UI immediately and then send the API request. If the request fails, revert the UI state and show an error. This makes the app feel faster.
  • Clear UX for Empty States: If data loading completes but there's no data (e.g., an empty search result), show a friendly message like 'No items found' instead of just a blank screen.

Practice Exercises


1. Basic User Profile Loader: Create a component that fetches a single user's data from `https://jsonplaceholder.typicode.com/users/1`. Display an `ActivityIndicator` while loading, the user's name and email on success, and a simple error message if the fetch fails.
2. Toggle Error State: Modify the basic example to include a button that, when pressed, simulates a network error (e.g., by trying to fetch from a non-existent URL or throwing an error in the `try` block). Ensure the error message is displayed and a 'Retry' button appears.
3. Conditional Content Display: Build a component that fetches a list of products. If the list is empty after successful loading, display 'No products available.' instead of an empty list. Otherwise, display the product titles in a `FlatList`.

Mini Project / Task


Build a simple 'Weather App' screen. It should:
1. Fetch weather data for a hardcoded city (e.g., 'London') from a public API (e.g., OpenWeatherMap - you'll need an API key, or use a mock API).
2. Display a loading spinner and 'Fetching weather...' text while the API call is in progress.
3. On successful fetch, display the city name, temperature, and a brief description of the weather.
4. If the fetch fails (e.g., network error, invalid API key, city not found), display a clear error message and a 'Refresh' button to retry the request.

Challenge (Optional)


Extend the 'Weather App' screen to include a search bar. When the user types a city name and presses a 'Get Weather' button, fetch and display the weather for that city. Implement a 'skeleton screen' loading state specifically for the weather details, so the user sees a layout placeholder rather than just a spinner while the data is being fetched. Also, handle the case where the API returns no data or an error for the searched city, providing specific feedback (e.g., 'City not found').

AsyncStorage

AsyncStorage is a simple, persistent, key-value storage system used in React Native apps to save small amounts of data directly on the device. It exists so apps can remember information between launches, such as theme preference, onboarding status, saved tokens, draft text, or lightweight user settings. In real mobile apps, AsyncStorage is often used for “remember me” features, app preferences, cached form values, and flags like whether a user has completed a tutorial. It is not a full database, and it is not meant for highly sensitive data like passwords unless combined with secure storage solutions. AsyncStorage works asynchronously, which means reading and writing data takes place without blocking the user interface. That makes the app feel smoother while storage operations happen in the background.

The main idea is that data is stored as strings under unique keys. If you want to save objects or arrays, you convert them with JSON.stringify() before saving, and restore them with JSON.parse() after reading. Common operations include saving data with setItem, reading with getItem, deleting with removeItem, clearing everything with clear, and working with many values using methods like multiSet and multiGet. Think of it as a small local memory shelf for your app, useful for lightweight persistence but not ideal for large structured datasets.

Step-by-Step Explanation

First, install the package commonly used in modern React Native projects: @react-native-async-storage/async-storage. Then import it into your component. To save a value, call AsyncStorage.setItem('key', 'value'). Because it returns a Promise, use await inside an async function. To read a value, use AsyncStorage.getItem('key'). If the key does not exist, the result is usually null. For objects, convert before saving and parse after loading. Always wrap storage calls in try...catch so your app can handle failures safely. Use clear naming for keys, such as user_theme or onboarding_completed, to avoid confusion later.

Comprehensive Code Examples

Basic example
import AsyncStorage from '@react-native-async-storage/async-storage';

const saveName = async () => {
try {
await AsyncStorage.setItem('username', 'Aisha');
} catch (error) {
console.log('Save error:', error);
}
};

const loadName = async () => {
try {
const value = await AsyncStorage.getItem('username');
console.log(value);
} catch (error) {
console.log('Load error:', error);
}
};
Real-world example
import React, { useEffect, useState } from 'react';
import { View, Text, Switch } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

export default function ThemeSettings() {
const [darkMode, setDarkMode] = useState(false);

useEffect(() => {
const loadTheme = async () => {
const saved = await AsyncStorage.getItem('dark_mode');
if (saved !== null) setDarkMode(JSON.parse(saved));
};
loadTheme();
}, []);

const toggleTheme = async value => {
setDarkMode(value);
await AsyncStorage.setItem('dark_mode', JSON.stringify(value));
};

return (
Dark Mode

);
}
Advanced usage
import AsyncStorage from '@react-native-async-storage/async-storage';

const saveProfile = async profile => {
try {
await AsyncStorage.multiSet([
['user_name', profile.name],
['user_email', profile.email],
['user_meta', JSON.stringify(profile.meta)],
]);
} catch (error) {
console.log(error);
}
};

const loadProfile = async () => {
try {
const values = await AsyncStorage.multiGet(['user_name', 'user_email', 'user_meta']);
console.log(values);
} catch (error) {
console.log(error);
}
};

Common Mistakes

  • Saving objects directly: AsyncStorage stores strings, so use JSON.stringify() before saving objects.
  • Forgetting await: storage methods are asynchronous; without await, your logic may run too early.
  • Ignoring null values: getItem() may return null, so check before parsing or using the result.
  • Using it for secrets: sensitive tokens should go into secure storage, not plain local storage alone.

Best Practices

  • Use consistent key names with prefixes like app_ or user_.
  • Keep stored data small and focused on lightweight persistence.
  • Wrap all read and write operations in try...catch blocks.
  • Serialize and parse carefully to avoid crashes from invalid JSON.
  • Load stored values during app startup or screen mount for a smooth user experience.

Practice Exercises

  • Create a screen that saves and loads a user nickname using AsyncStorage.
  • Build a toggle that stores whether notifications are enabled and restores the value on app reopen.
  • Save a small object containing a city name and country, then display it after loading.

Mini Project / Task

Build a simple notes draft saver that stores text from a TextInput and restores it automatically when the user returns to the app.

Challenge (Optional)

Create a recent-search history feature that stores the last five search terms, prevents duplicates, and reloads them when the app starts.

SecureStore and Sensitive Data

When building mobile apps, some information must be protected more carefully than normal app data. Examples include authentication tokens, refresh tokens, API secrets issued for a user session, encrypted identifiers, or small pieces of personal data. Storing these values in plain local storage is risky because another app, a rooted device, backups, or debugging tools may expose them. In React Native projects, developers commonly use secure storage solutions such as Expo SecureStore or platform-backed keychain services to keep sensitive values in protected system storage. On iOS, this usually maps to Keychain, and on Android, it often uses encrypted preferences or keystore-backed protection.

Secure storage exists because mobile apps often need to remember a user between launches without repeatedly asking them to log in. Real-world apps like banking tools, e-commerce apps, healthcare portals, and enterprise dashboards all need a safe place to keep short-lived credentials. A key concept is that SecureStore is best for small sensitive values, not large databases or entire user profiles. Another important idea is data classification. Public UI preferences such as theme mode can go into AsyncStorage, while access tokens and secrets should go into SecureStore. Some secure storage APIs also support options such as requiring device authentication, controlling accessibility after reboot, or deleting values on logout.

Step-by-Step Explanation

In a React Native app using Expo, install and import SecureStore. The main methods are usually setItemAsync, getItemAsync, and deleteItemAsync. First, choose a clear key name such as authToken. Next, save the sensitive value after login. Then, read it when the app starts to restore the user session. Finally, remove it during logout. Beginners should remember that these methods are asynchronous, so they must be used with await or promises. Also, never hardcode secrets directly in the app source because app bundles can be inspected.


Comprehensive Code Examples

import * as SecureStore from 'expo-secure-store';

async function saveToken() {
await SecureStore.setItemAsync('authToken', 'abc123');
}

async function loadToken() {
const token = await SecureStore.getItemAsync('authToken');
console.log(token);
}
import React, { useEffect, useState } from 'react';
import { View, Text, Button } from 'react-native';
import * as SecureStore from 'expo-secure-store';

export default function App() {
const [token, setToken] = useState(null);

useEffect(() => {
const restoreSession = async () => {
const savedToken = await SecureStore.getItemAsync('authToken');
setToken(savedToken);
};
restoreSession();
}, []);

const handleLogin = async () => {
const fakeToken = 'user-session-token';
await SecureStore.setItemAsync('authToken', fakeToken);
setToken(fakeToken);
};

const handleLogout = async () => {
await SecureStore.deleteItemAsync('authToken');
setToken(null);
};

return (

{token ? 'Logged in' : 'Logged out'}
import * as SecureStore from 'expo-secure-store';

export async function saveSecureValue(key, value) {
try {
await SecureStore.setItemAsync(key, value, {
keychainService: 'myapp-secure',
});
} catch (error) {
console.error('Secure save failed', error);
}
}

export async function getSecureValue(key) {
try {
return await SecureStore.getItemAsync(key);
} catch (error) {
console.error('Secure read failed', error);
return null;
}
}

Common Mistakes

  • Saving passwords in AsyncStorage instead of secure storage. Fix: keep passwords and tokens only in SecureStore or platform keychain solutions.
  • Forgetting await on secure storage calls. Fix: always handle async reads and writes properly.
  • Storing too much data in SecureStore. Fix: store only small sensitive values and keep larger non-sensitive data elsewhere.
  • Not deleting tokens on logout. Fix: clear secure values when the session ends.

Best Practices

  • Store only what is necessary, such as access and refresh tokens.
  • Use descriptive key names and centralize secure-storage helper functions.
  • Combine secure storage with token expiration checks and backend validation.
  • Treat device storage as one layer of security, not the only layer.
  • Avoid logging sensitive values to the console.

Practice Exercises

  • Create a function that saves a user token in SecureStore and another function that reads it back.
  • Build a simple login/logout screen that updates text based on whether a token exists.
  • Write a startup check that loads a saved token and displays a welcome message only if the token is found.

Mini Project / Task

Build a session manager for a React Native app that stores a login token securely, restores it when the app opens, and deletes it on logout.

Challenge (Optional)

Create a reusable secure storage service that can save, load, and delete multiple sensitive values while handling errors consistently across the app.

Context API for State

The Context API is React Native's built-in way to share data across many components without passing props manually through every level of the component tree. This problem is called prop drilling, and it becomes common when values such as theme, authenticated user data, language settings, cart items, or app preferences are needed in many screens. In real mobile apps, Context is often used for global state that changes occasionally and must be available in multiple places. For example, a shopping app may store the current user and cart count in context so the home screen, profile screen, and checkout screen can all access the same data.


Context works through three main parts: createContext, a Provider, and consuming the context with useContext or a Consumer component. The context object defines what data can be shared. The Provider wraps part of the app and supplies the current value. Any child inside that Provider can read the value. Although Context is useful, it is not a replacement for every form of state management. Local component state should still stay local when only one component needs it. Context is best for shared state that belongs to a feature area or the entire app.


Step-by-Step Explanation

First, create a context using createContext(). This creates a container for shared data. Second, build a Provider component. Inside it, store state with useState or useReducer, then pass values and functions through the Provider's value prop. Third, wrap your app or a section of the app with that Provider. Finally, inside child components, call useContext(YourContext) to read the shared state and update functions.


A common pattern is to export both the context and a custom hook such as useTheme() or useAuth(). This keeps code cleaner and prevents repeated imports of useContext. Context can hold simple values like a string or more complex objects containing state and actions. If the value object changes too often, many children may re-render, so it is important to keep the context focused and organized.


Comprehensive Code Examples

Basic example:

import React, { createContext, useContext, useState } from 'react';
import { View, Text, Button } from 'react-native';

const ThemeContext = createContext();

function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light');

return (

{children}

);
}

function HomeScreen() {
const { theme, toggleTheme } = useContext(ThemeContext);

return (

Current theme: {theme}

Real-world example:

const AuthContext = createContext();

function AuthProvider({ children }) {
const [user, setUser] = useState(null);

const login = (name) => setUser({ name });
const logout = () => setUser(null);

return (

{children}

);
}

Advanced usage with custom hook:

const SettingsContext = createContext();

export function SettingsProvider({ children }) {
const [notificationsEnabled, setNotificationsEnabled] = useState(true);

return (
value={{ notificationsEnabled, setNotificationsEnabled }}
>
{children}

);
}

export function useSettings() {
return useContext(SettingsContext);
}

Common Mistakes

  • Forgetting to wrap components with the Provider: consumers receive undefined or default values. Fix: wrap the correct app subtree with the Provider.
  • Putting too much state in one context: unrelated updates trigger extra re-renders. Fix: split contexts by feature such as auth, theme, and settings.
  • Using Context for deeply local state: this makes code harder to maintain. Fix: keep state local unless multiple components truly need it.

Best Practices

  • Create feature-based contexts instead of one giant global context.
  • Export custom hooks for safer and cleaner context access.
  • Store both state and action functions in the Provider value.
  • Use useReducer when state transitions become complex.
  • Keep Provider placement as narrow as possible to reduce unnecessary re-renders.

Practice Exercises

  • Create a LanguageContext that stores the current language and a function to switch between English and Spanish.
  • Build a ThemeContext and show the selected theme text in two different components.
  • Create a UserContext with a username and an update button that changes the name from one screen component.

Mini Project / Task

Build a small app settings module with a context that stores dark mode and notification preferences, then display and update those settings from two separate components.


Challenge (Optional)

Create an AuthContext that supports login, logout, and protected screen rendering. Show one component when the user is logged in and another when no user exists.

Redux Toolkit in React Native

Redux Toolkit is the modern, recommended way to manage global state in React Native apps. State means the data your app uses and updates, such as logged-in user details, cart items, theme settings, notifications, or API results. In small apps, local component state with useState is enough, but as apps grow, passing data through many components becomes difficult. Redux Toolkit exists to simplify Redux by reducing boilerplate, improving readability, and making state updates safer. In real mobile apps, it is used for authentication flows, product catalogs, offline-friendly data caching, favorites lists, and synchronizing UI across many screens.

Redux Toolkit mainly includes a centralized store, slices, reducers, actions, and async logic. The store is the single source of truth. A slice groups related state and update logic together. Reducers describe how state changes. Actions are events that trigger those changes. With createAsyncThunk, you can handle API requests cleanly. In React Native, Redux Toolkit is commonly paired with react-redux so screens can read state using useSelector and update it using useDispatch.

Step-by-Step Explanation

First, install dependencies: @reduxjs/toolkit and react-redux. Next, create a slice with initial state and reducer functions. Then configure the Redux store using configureStore. After that, wrap your app with Provider so all components can access the store. Inside components, use useSelector to read values and useDispatch to send actions.

A typical flow works like this: define initial state, create reducers such as increment, decrement, or addItem, export actions, create the store, and connect the app. For API calls, define an async thunk that fetches data, then handle loading, success, and error states inside extraReducers. This structure keeps logic organized and predictable.

Comprehensive Code Examples

Basic example

import { configureStore, createSlice } from '@reduxjs/toolkit';
import { Provider, useDispatch, useSelector } from 'react-redux';
import { View, Text, Button } from 'react-native';

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

const store = configureStore({ reducer: { counter: counterSlice.reducer } });
const { increment, decrement } = counterSlice.actions;

function CounterScreen() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (

Count: {count}

Real-world example

const cartSlice = createSlice({
name: 'cart',
initialState: { items: [] },
reducers: {
addToCart: (state, action) => { state.items.push(action.payload); },
removeFromCart: (state, action) => {
state.items = state.items.filter(item => item.id !== action.payload);
}
}
});

Advanced usage

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

export const fetchProducts = createAsyncThunk('products/fetch', async () => {
const response = await fetch('https://fakestoreapi.com/products');
return await response.json();
});

const productsSlice = createSlice({
name: 'products',
initialState: { items: [], loading: false, error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchProducts.pending, (state) => { state.loading = true; })
.addCase(fetchProducts.fulfilled, (state, action) => {
state.loading = false;
state.items = action.payload;
})
.addCase(fetchProducts.rejected, (state) => {
state.loading = false;
state.error = 'Failed to load products';
});
}
});

Common Mistakes

  • Forgetting Provider: If the app is not wrapped in Provider, Redux hooks will fail. Fix: wrap the root component with Provider store={store}.
  • Mutating state outside reducers: Directly editing arrays in components causes bugs. Fix: dispatch actions and let reducers manage updates.
  • Using too much global state: Not every value belongs in Redux. Fix: keep temporary UI state like modal visibility local when possible.

Best Practices

  • Organize by feature: Keep slices in feature folders like features/cart or features/auth.
  • Use async thunk for requests: Track loading, error, and data clearly.
  • Keep state minimal: Store only what the app truly needs globally.
  • Create selectors: Reuse state-reading logic for cleaner components.

Practice Exercises

  • Create a Redux Toolkit counter with increment, decrement, and reset actions.
  • Build a favorites slice that adds and removes movie IDs from a list.
  • Create a products slice with loading and error fields for an API fetch.

Mini Project / Task

Build a simple shopping cart screen in React Native using Redux Toolkit. Users should be able to add products, remove products, and view the total number of items in the cart.

Challenge (Optional)

Create an authentication slice that stores user information, login status, loading state, and error messages, then connect it to a login form screen.

Camera and Media Access


Accessing the camera and media library is a fundamental requirement for many modern mobile applications. Think of social media apps like Instagram or Snapchat, messaging apps like WhatsApp, or even utility apps that allow you to scan documents or create photo collages. React Native, being a framework for building native mobile applications, provides robust ways to interact with device hardware, including the camera and media storage. This capability allows developers to build rich, interactive experiences where users can capture photos, record videos, and select existing media from their device's gallery. Without direct access to these features, many common app functionalities would be impossible, severely limiting the scope and utility of mobile applications. React Native bridges the gap between JavaScript code and native device APIs, enabling seamless integration of camera and media functionalities across both iOS and Android platforms with a unified codebase.


While React Native's core doesn't provide a built-in API for camera and media access, it leverages community-driven libraries that wrap the native functionalities. The most popular and widely adopted library for this purpose is react-native-image-picker. Another prominent option, especially for advanced camera controls and live video processing, is react-native-camera. Both libraries offer distinct advantages and are chosen based on the specific requirements of the application.


  • react-native-image-picker: This library focuses on providing a simple API to pick images or videos from the device's gallery or capture new ones using the device's camera. It handles permissions, image resizing, and various media options. It's ideal for scenarios where you need to get a single image or video file from the user with minimal fuss.
  • react-native-camera: This library offers more granular control over the camera hardware itself. It allows you to build custom camera UIs, access raw camera streams, apply filters, scan barcodes/QR codes, and perform advanced tasks like face detection. It's suitable for applications that require a fully customized camera experience or advanced real-time processing.

Step-by-Step Explanation


Let's focus on react-native-image-picker for a common use case: picking an image or taking a photo. The process generally involves:


  1. Installation: Add the library to your project.
  2. Linking (if necessary): For older React Native versions or specific libraries, manual linking might be required. Modern React Native often auto-links.
  3. Permissions: Configure platform-specific permissions in Info.plist (iOS) and AndroidManifest.xml (Android) to access the camera and photo library.
  4. Importing: Import the library into your JavaScript component.
  5. Launching the Picker/Camera: Call the appropriate function (launchCamera or launchImageLibrary) with desired options.
  6. Handling the Response: Process the returned data, which typically includes the URI of the selected/captured media.
  7. Displaying/Uploading: Use the URI to display the image/video or upload it to a server.

Comprehensive Code Examples


Basic Example: Picking an Image from the Gallery

This example demonstrates how to pick a single image from the device's photo library using react-native-image-picker.


import React, { useState } from 'react';
import { Button, Image, View, Text, StyleSheet, Alert } from 'react-native';
import { launchImageLibrary } from 'react-native-image-picker';

const ImagePickerGallery = () => {
const [selectedImage, setSelectedImage] = useState(null);

const pickImage = async () => {
const options = {
mediaType: 'photo',
quality: 1,
};

launchImageLibrary(options, response => {
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.errorCode) {
console.error('ImagePicker Error: ', response.errorCode);
Alert.alert('Error', 'Failed to pick image: ' + response.errorMessage);
} else if (response.assets && response.assets.length > 0) {
setSelectedImage(response.assets[0].uri);
}
});
};

return (

Pick Image from Gallery

Real-World Example: Taking a Photo with Camera and Displaying

This example demonstrates how to capture a photo using the device's camera and display it.


import React, { useState } from 'react';
import { Button, Image, View, Text, StyleSheet, Alert } from 'react-native';
import { launchCamera } from 'react-native-image-picker';

const CameraCapture = () => {
const [capturedImage, setCapturedImage] = useState(null);

const takePhoto = async () => {
const options = {
mediaType: 'photo',
quality: 0.8,
cameraType: 'back', // 'front' or 'back'
};

launchCamera(options, response => {
if (response.didCancel) {
console.log('User cancelled camera');
} else if (response.errorCode) {
console.error('Camera Error: ', response.errorCode);
Alert.alert('Error', 'Failed to take photo: ' + response.errorMessage);
} else if (response.assets && response.assets.length > 0) {
setCapturedImage(response.assets[0].uri);
}
});
};

return (

Capture Photo with Camera

Advanced Usage: Selecting Multiple Images and Uploading (Conceptual)

This example outlines the logic for selecting multiple images and preparing them for upload. Actual upload logic would involve a backend service.


import React, { useState } from 'react';
import { Button, Image, View, Text, StyleSheet, Alert, ScrollView } from 'react-native';
import { launchImageLibrary } from 'react-native-image-picker';

const MultiImageSelector = () => {
const [selectedImages, setSelectedImages] = useState([]);
const [uploading, setUploading] = useState(false);

const pickMultipleImages = async () => {
const options = {
mediaType: 'photo',
selectionLimit: 5, // Allow selecting up to 5 images
quality: 0.7,
};

launchImageLibrary(options, response => {
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.errorCode) {
console.error('ImagePicker Error: ', response.errorCode);
Alert.alert('Error', 'Failed to pick images: ' + response.errorMessage);
} else if (response.assets) {
setSelectedImages(response.assets.map(asset => asset.uri));
}
});
};

const uploadImages = async () => {
if (selectedImages.length === 0) {
Alert.alert('No Images', 'Please select images first.');
return;
}
setUploading(true);
Alert.alert('Uploading', `Uploading ${selectedImages.length} images... (Simulated)`);
// In a real app, you would iterate through selectedImages
// and use FormData to send them to a backend API.
// Example:
// const formData = new FormData();
// selectedImages.forEach((uri, index) => {
// formData.append(`photo_${index}`, {
// uri: uri,
// type: 'image/jpeg', // or 'image/png' based on file type
// name: `upload_${index}.jpg`,
// });
// });
// await fetch('YOUR_UPLOAD_API_ENDPOINT', {
// method: 'POST',
// body: formData,
// headers: {
// 'Content-Type': 'multipart/form-data',
// },
// });
setTimeout(() => { // Simulate upload time
Alert.alert('Success', 'Images uploaded successfully!');
setSelectedImages([]);
setUploading(false);
}, 2000);
};

return (

Select & Upload Multiple Images

Common Mistakes


  • Forgetting Permissions: The most common issue. Developers often forget to declare camera and photo library permissions in AndroidManifest.xml (Android) and Info.plist (iOS).
    Fix: Always add the necessary permission strings (e.g., NSCameraUsageDescription, NSPhotoLibraryUsageDescription for iOS, and CAMERA, READ_EXTERNAL_STORAGE for Android).
  • Incorrect URI Handling: The URI returned by the picker/camera might be a local file path or a content URI. Directly using it in an component usually works, but when uploading, you might need to convert it or ensure it's accessible by your upload mechanism.
    Fix: Ensure you understand the structure of the returned URI. For uploads, you often need to create a FormData object and append the file with its correct type and name.
  • Ignoring Error Codes and Cancellations: Not handling response.didCancel or response.errorCode can lead to silent failures or a poor user experience.
    Fix: Always check for these properties in the response object and provide appropriate feedback to the user or log errors for debugging.

Best Practices


  • Request Permissions Gracefully: Instead of just crashing if permissions are denied, explain to the user why you need camera/photo access and guide them to settings if they initially deny it. Use libraries like react-native-permissions for a more robust permission flow.
  • Handle Large Files: Images and videos can be very large. Implement client-side resizing or compression before uploading to save bandwidth and storage, and improve upload speed.
  • Provide User Feedback: When an image is picked or captured, show a loading indicator during processing/upload, or display a preview of the selected media.
  • Error Handling and Fallbacks: Always include comprehensive error handling. If camera access fails, provide an alternative (e.g., selecting from a gallery).
  • Platform-Specific UI/UX: While React Native aims for cross-platform, sometimes making minor platform-specific adjustments to the camera UI or picker experience can enhance usability.

Practice Exercises


  1. Basic Image Display: Create a React Native component with a button. When pressed, allow the user to select an image from their gallery and display it in an component.
  2. Toggle Camera/Gallery: Build a screen with two buttons: 'Take Photo' and 'Choose from Gallery'. Implement both functionalities using react-native-image-picker and display the selected/captured image.
  3. Video Selection: Modify one of the previous examples to allow the user to select a video from their gallery instead of an image. Display a message with the video's URI (you don't need to play the video for this exercise).

Mini Project / Task


Develop a simple 'Profile Picture Uploader' screen. It should:
1. Display a circular placeholder image.
2. Have a button that, when pressed, presents the user with two options: 'Take Photo' (using camera) or 'Choose from Library'.
3. After an image is selected/captured, update the circular placeholder to display the new profile picture.
4. (Optional) Add a 'Save Profile' button that would conceptually 'upload' the image (you can just log the URI to the console).
Remember to include all necessary permissions.


Challenge (Optional)


Using react-native-camera (install and configure this library), create a custom camera screen. This screen should display a live camera feed and have a button to take a photo. Instead of using the default camera UI, you should render the camera view directly within your component. Display the captured photo after it's taken.

Location and Maps

Location and maps in React Native let your app understand where a user is and display that information visually. This is used in ride-sharing apps, food delivery, fitness trackers, travel guides, weather apps, and store locators. The location part usually means reading GPS or network-based coordinates such as latitude and longitude. The maps part means showing those coordinates on an interactive map, often with markers, routes, and regions. In real projects, developers commonly use geolocation APIs for coordinates and a library such as react-native-maps for rendering maps.

The topic has a few important parts. First, permissions: mobile apps must ask users before accessing location. On Android, this often requires runtime permission handling, while iOS needs purpose strings in app configuration. Second, location retrieval: apps may fetch the current position once or watch changes continuously. Third, map rendering: you define an initial region, place markers, and respond to presses or camera movement. Fourth, reverse geocoding or nearby search can convert coordinates into meaningful addresses or places using external APIs.

Step-by-Step Explanation

Start by installing a map library and a location package that fits your stack. Then configure platform permissions. After that, request permission when the app opens or when the user taps a button. If permission is granted, call a method to get the current coordinates. Store those coordinates in state using useState. Pass the state into a map component as a region or use it to place a marker. For a region, you usually provide latitude, longitude, latitudeDelta, and longitudeDelta. The delta values control zoom level. If you want live tracking, subscribe to location updates and clear the watcher when the component unmounts.

Comprehensive Code Examples

import React, { useState } from 'react';
import { View, Button, Text } from 'react-native';
import MapView, { Marker } from 'react-native-maps';

export default function App() {
const [region, setRegion] = useState({
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
});

return (

Basic map example




);
}
import React, { useState } from 'react';
import { View, Button, PermissionsAndroid, Platform } from 'react-native';
import Geolocation from '@react-native-community/geolocation';
import MapView, { Marker } from 'react-native-maps';

export default function StoreLocator() {
const [location, setLocation] = useState(null);

const getLocation = async () => {
if (Platform.OS === 'android') {
await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
}

Geolocation.getCurrentPosition(position => {
const { latitude, longitude } = position.coords;
setLocation({ latitude, longitude, latitudeDelta: 0.01, longitudeDelta: 0.01 });
});
};

return (

import React, { useEffect, useRef, useState } from 'react';
import { View } from 'react-native';
import Geolocation from '@react-native-community/geolocation';
import MapView, { Marker, Polyline } from 'react-native-maps';

export default function LiveTracker() {
const [points, setPoints] = useState([]);
const watchId = useRef(null);

useEffect(() => {
watchId.current = Geolocation.watchPosition(pos => {
const point = { latitude: pos.coords.latitude, longitude: pos.coords.longitude };
setPoints(prev => [...prev, point]);
});

return () => Geolocation.clearWatch(watchId.current);
}, []);

return (


{points.length > 0 && }



);
}

Common Mistakes

  • Skipping permissions: The app fails silently. Always request and verify permission before reading location.
  • Using invalid region data: Forgetting latitudeDelta and longitudeDelta can break map display. Include both values.
  • Not cleaning watchers: Continuous tracking may drain battery and leak resources. Clear subscriptions on unmount.

Best Practices

  • Ask for location only when needed, not immediately without context.
  • Show loading and permission-denied states clearly.
  • Use lower-accuracy tracking when precise GPS is unnecessary to save battery.
  • Keep coordinates in state and derive markers or routes from that single source of truth.
  • Test on real devices because simulators may not reflect real GPS behavior.

Practice Exercises

  • Build a screen with a button that gets the current location and places one marker on a map.
  • Create a map that starts at a default city and updates to the user location after permission is granted.
  • Track movement with a watcher and draw a polyline connecting all recorded points.

Mini Project / Task

Build a simple store locator that gets the user location, displays it on a map, and adds two hard-coded nearby store markers.

Challenge (Optional)

Extend the store locator so tapping a marker opens a details card showing store name, distance from the user, and whether location permission is enabled.

Push Notifications

Push notifications are messages sent to a user's device even when the app is closed or running in the background. In React Native, they are used to re-engage users, deliver reminders, show order updates, announce chat messages, and provide time-sensitive alerts. Real-world apps such as food delivery, banking, e-commerce, and messaging platforms rely on push notifications to keep users informed without requiring them to open the app manually.

In practice, push notifications involve three parts: the client app, a push provider, and your backend or notification service. Common providers include Firebase Cloud Messaging for Android and Apple Push Notification service for iOS. In React Native projects, developers often use Expo Notifications for managed workflows or libraries such as @react-native-firebase/messaging with Notifee in bare workflows. Notifications can be remote or local. Remote notifications are sent from a server through APNs or FCM. Local notifications are scheduled directly by the app on the device. You should also understand foreground, background, and quit states because notification behavior changes depending on whether the app is open, minimized, or closed.

Step-by-Step Explanation

First, install the notification library that matches your project setup. For Expo apps, expo-notifications is a common choice. Next, request permission from the user because both iOS and newer Android versions require explicit approval. After permission is granted, retrieve the device push token. This token identifies the device for the push provider. Then store that token securely on your backend so your server can send notifications later.

When sending a push message, your server targets the saved token and includes payload data such as title, body, and custom fields like screen name or order id. In the app, create listeners for received notifications and notification responses. One listener handles notifications arriving while the app is in the foreground. Another listener reacts when the user taps a notification. Finally, navigate the user to the correct screen based on the payload.

Comprehensive Code Examples

import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import { useEffect, useState } from 'react';

export default function App() {
const [token, setToken] = useState(null);

useEffect(() => {
registerForPushNotificationsAsync().then(setToken);
}, []);

async function registerForPushNotificationsAsync() {
if (!Device.isDevice) return null;
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') return null;
const tokenData = await Notifications.getExpoPushTokenAsync();
return tokenData.data;
}
}
import * as Notifications from 'expo-notifications';

Notifications.scheduleNotificationAsync({
content: {
title: 'Reminder',
body: 'Complete your lesson today',
data: { screen: 'Lessons' }
},
trigger: { seconds: 5 }
});
import * as Notifications from 'expo-notifications';
import { useEffect } from 'react';

export function NotificationHandler({ navigation }) {
useEffect(() => {
const receivedSub = Notifications.addNotificationReceivedListener(notification => {
console.log('Foreground notification:', notification);
});

const responseSub = Notifications.addNotificationResponseReceivedListener(response => {
const screen = response.notification.request.content.data.screen;
if (screen) {
navigation.navigate(screen);
}
});

return () => {
receivedSub.remove();
responseSub.remove();
};
}, [navigation]);

return null;
}

Common Mistakes

  • Skipping permission handling: Always request and verify permission before fetching a token.
  • Testing on simulators only: Push notifications often require a real device, especially for token generation.
  • Not saving the token on the backend: Without persistent storage, your server cannot target the user later.
  • Ignoring app state differences: Foreground and background notifications may require different handling logic.

Best Practices

  • Send relevant notifications only: Avoid spamming users and provide clear value.
  • Use payload data for navigation: Include screen names or identifiers to open the correct app view.
  • Handle token refresh: Update stored tokens when they change.
  • Respect platform guidelines: Configure channels on Android and permission messaging carefully on iOS.
  • Keep messages concise: Short, actionable text improves engagement.

Practice Exercises

  • Create a React Native screen that requests notification permission and displays the returned push token.
  • Schedule a local notification that appears 10 seconds after tapping a button.
  • Add a notification response listener that opens a specific screen when the user taps the alert.

Mini Project / Task

Build a study reminder feature for a learning app that lets the user schedule one local notification per day and opens the lessons screen when tapped.

Challenge (Optional)

Extend the reminder feature so users can choose different notification categories such as practice, revision, or deadline alerts, and navigate to different screens based on the notification payload.

Deep Linking

Deep linking lets a mobile app open a specific screen or perform a specific action from a URL. Instead of just launching the app home screen, a deep link can take the user directly to a product page, password reset screen, profile page, or promotional offer. In React Native, deep linking is important because mobile apps often interact with emails, SMS messages, websites, QR codes, push notifications, and other apps. For example, a shopping app may open myshop://product/42, while a public web-compatible link might look like https://myshop.com/product/42. There are two common forms: custom URL schemes and universal links or app links. A custom scheme uses your own protocol name and is easy to configure, but it may conflict with other apps and does not work as naturally on the web. Universal links on iOS and app links on Android use regular HTTPS URLs, making them better for user trust, SEO, and smooth web-to-app transitions. In React Native, deep linking is typically handled with the built-in Linking API and often integrated with React Navigation so incoming URLs map directly to screens. You usually configure native platform files first, then write JavaScript that listens for incoming links and parses route parameters.

Step-by-Step Explanation

First, define the URL patterns your app should support, such as myapp://home or https://example.com/profile/7. Second, configure Android intent filters and iOS URL types or associated domains so the operating systems know your app can open those links. Third, in React Native, use Linking.getInitialURL() to check whether the app was launched by a link. Fourth, add a listener with Linking.addEventListener('url', ...) so links received while the app is already open are handled too. Fifth, parse the URL and route the user to the right screen. If you use React Navigation, provide a linking configuration with prefixes and screen patterns so navigation happens automatically. Parameters such as item IDs can be extracted from the URL path. Finally, test both cold start and foreground cases on Android and iOS because behavior differs between platforms and installation states.

Comprehensive Code Examples

import React, { useEffect } from 'react';
import { View, Text, Linking } from 'react-native';

export default function App() {
useEffect(() => {
Linking.getInitialURL().then(url => {
if (url) console.log('Opened from:', url);
});

const sub = Linking.addEventListener('url', ({ url }) => {
console.log('Received link:', url);
});

return () => sub.remove();
}, []);

return Deep Linking Demo;
}
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

const Stack = createNativeStackNavigator();

const linking = {
prefixes: ['myapp://', 'https://example.com'],
config: {
screens: {
Home: 'home',
Profile: 'profile/:id',
},
},
};

function App() {
return (






);
}
import React from 'react';
import { View, Text } from 'react-native';

function ProfileScreen({ route }) {
const { id } = route.params;
return (

User Profile: {id}

);
}

A real-world flow is a password reset email containing https://example.com/reset/abc123. The app opens the Reset screen, reads the token, validates it, and lets the user set a new password. An advanced setup may combine deep links with authentication guards so the app stores the intended destination, asks the user to log in, then redirects to the original screen afterward.

Common Mistakes

  • Forgetting native setup: JavaScript code alone is not enough. Add Android intent filters and iOS URL configuration correctly.
  • Handling only initial launch: Also listen for runtime URL events when the app is already open.
  • Wrong route patterns: Make sure URL paths match your React Navigation config exactly, including dynamic parameters.
  • No fallback logic: If parsing fails, send users to a safe default screen instead of crashing.

Best Practices

  • Prefer HTTPS universal links or app links for production apps when possible.
  • Keep URL structures simple, readable, and versionable.
  • Validate all incoming parameters before using them in API calls or navigation.
  • Test links from browser, notes app, email, QR codes, and push notifications.
  • Add analytics to track which links are opened and where users drop off.

Practice Exercises

  • Create a custom scheme like mydemo://home and log the URL when the app opens.
  • Configure React Navigation so mydemo://profile/25 opens a Profile screen and shows the ID.
  • Build a handler that redirects unknown links to a Not Found screen.

Mini Project / Task

Build a small e-commerce app flow where a link like myshop://product/101 opens a Product Details screen and displays the product ID from the URL.

Challenge (Optional)

Implement protected deep linking so a URL to myapp://orders/500 first checks whether the user is logged in, then navigates to Login if needed, and finally returns to the Orders screen after successful authentication.

App Icons and Splash Screens


App icons and splash screens are crucial elements of a mobile application's branding and user experience. The app icon is the first visual representation of your application that users encounter on their device's home screen, in app stores, and in settings. It serves as a visual identifier and influences a user's decision to download and launch your app. A well-designed icon is memorable, recognizable, and reflects the app's purpose. Splash screens, also known as launch screens or loading screens, are displayed immediately after a user taps your app's icon and before the main content of the app loads. Their primary purpose is to provide a smooth visual transition, mask loading times, and offer an immediate brand presence, preventing the user from seeing a blank or unresponsive screen. Without them, users might perceive your app as slow or broken during the initial setup or data loading phase. In real-world scenarios, virtually every professional mobile application utilizes both a custom app icon and a splash screen to enhance branding, improve perceived performance, and create a polished user experience from the very first interaction.

While React Native handles many cross-platform aspects, app icons and splash screens often require platform-specific configurations due to differences in operating system guidelines and asset requirements. For instance, iOS requires icons in various sizes for different devices and contexts (e.g., iPhone, iPad, Spotlight, Settings), and splash screens are typically configured via a LaunchScreen.storyboard file. Android also demands multiple icon densities (mdpi, hdpi, xhdpi, etc.) to ensure clarity across diverse screen resolutions, and splash screens can be implemented using themes or dedicated splash screen libraries. Understanding these platform-specific nuances is key to delivering a consistent and high-quality user experience across both iOS and Android.

Step-by-Step Explanation


Setting up app icons and splash screens in a React Native project typically involves two main approaches: manual configuration for basic setups and using community libraries for more robust and automated solutions. We'll focus on using a popular library, react-native-splash-screen, for splash screens, and manual configuration or a generator for app icons due to the varying requirements.

For App Icons:
1. Design your icon: Create a high-resolution square image (e.g., 1024x1024 pixels) for your app icon. Ensure it looks good at small sizes and follows platform-specific design guidelines (e.g., no transparency for iOS icons, adaptive icons for Android).
2. Generate platform-specific assets: Use an online icon generator like 'App Icon Generator' or 'MakeAppIcon' to generate all necessary sizes for iOS and Android from your single high-resolution image. This saves a lot of manual resizing.
3. Integrate into iOS:
    a. Open your iOS project in Xcode (ios/yourproject.xcworkspace).
    b. In the Project Navigator, select Images.xcassets.
    c. Select AppIcon.
    d. Drag and drop the generated iOS icon assets into their respective slots in Xcode. Xcode will show you exactly which size goes where.
4. Integrate into Android:
    a. Navigate to android/app/src/main/res/.
    b. You'll find folders like mipmap-hdpi, mipmap-mdpi, etc. Each contains an ic_launcher.png (and potentially ic_launcher_round.png for adaptive icons).
    c. Replace these default ic_launcher.png (and _round if applicable) files with your generated Android icon assets for each corresponding density folder. For adaptive icons, you'll need ic_launcher_foreground.xml and ic_launcher_background.xml in mipmap-anydpi-v26.

For Splash Screens using react-native-splash-screen:
1. Install the library:
npm install react-native-splash-screen
cd ios && pod install && cd .. // For iOS

2. Configure iOS:
    a. Open ios/yourproject.xcworkspace in Xcode.
    b. In the Project Navigator, right-click on your project name and select New File.... Choose Launch Screen under User Interface and name it LaunchScreen (if one doesn't exist).
    c. Open LaunchScreen.storyboard. You can add an ImageView and a Label for your logo and app name. Set constraints to center them.
    d. Add your splash screen image to Images.xcassets and reference it in the ImageView in LaunchScreen.storyboard.
    e. In your project's Info.plist, ensure the Launch Screen File key points to LaunchScreen.
    f. In AppDelegate.m (or .mm for Objective-C++), import SplashScreen.h and call [RNSplashScreen show]; before [super application:application didFinishLaunchingWithOptions:launchOptions];. Then, in your root JavaScript component, call SplashScreen.hide(); when your app is ready.
3. Configure Android:
    a. Create a splash screen drawable: In android/app/src/main/res/drawable/, create background_splash.xml.








    b. Add your splash image to android/app/src/main/res/mipmap-xxxx/ folders and name it splash_icon.png (or similar).
    c. Create colors.xml in android/app/src/main/res/values/ if it doesn't exist, and define your background color:


#FFFFFF


    d. In android/app/src/main/res/values/styles.xml, add a splash screen theme:






    e. In android/app/src/main/AndroidManifest.xml, change the theme of your main Activity to SplashScreenTheme and then switch back to AppTheme once the app loads:
    android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:theme="@style/SplashScreenTheme">






    f. In android/app/src/main/java/com/yourprojectname/MainActivity.java, import SplashScreen and call SplashScreen.show(this); in onCreate and switch the theme back:
import android.os.Bundle;
import com.facebook.react.ReactActivity;
import org.devio.rn.splashscreen.SplashScreen;

public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.show(this); // Show splash screen
super.onCreate(savedInstanceState);
setTheme(R.style.AppTheme); // Switch back to main app theme
}
// ... rest of your code ...
}

    g. Finally, in your React Native root component (e.g., App.js), hide the splash screen when your app is ready to render:
import React, { useEffect } from 'react';
import SplashScreen from 'react-native-splash-screen';

const App = () => {
useEffect(() => {
SplashScreen.hide(); // Hide splash screen when component mounts
}, []);

return (
// Your app's main content

Welcome to My App!

);
};

export default App;


Comprehensive Code Examples


Basic Example (Hiding Splash Screen)

This example shows the minimal setup to hide the splash screen once your React Native app is initialized.
// App.js
import React, { useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import SplashScreen from 'react-native-splash-screen';

const App = () => {
useEffect(() => {
// Hide the splash screen once the root component mounts
SplashScreen.hide();
}, []);

return (

My Awesome App Content

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
text: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});

export default App;


Real-world Example (Splash Screen with Async Data Loading)

In a real app, you might want to show the splash screen until some initial data is loaded or authentication is checked.
// App.js
import React, { useEffect, useState } from 'react';
import { View, Text, ActivityIndicator, StyleSheet } from 'react-native';
import SplashScreen from 'react-native-splash-screen';

const simulateDataLoad = () => {
return new Promise(resolve => {
setTimeout(() => {
console.log('Initial data loaded!');
resolve(true);
}, 3000); // Simulate 3 seconds of loading
});
};

const App = () => {
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
const initApp = async () => {
await simulateDataLoad(); // Wait for data to load
setIsLoading(false);
SplashScreen.hide(); // Hide splash screen only after data is ready
};

initApp();
}, []);

if (isLoading) {
// You might render a minimal loading indicator here if the splash screen
// is designed to transition into a loading state, or simply nothing if
// the splash screen handles all visual cues during loading.
return null; // Splash screen is visible, so we don't render anything else yet
}

return (

Welcome, User!
Your dashboard is ready.

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#E0F2F7',
},
text: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 10,
},
});

export default App;


Advanced Usage (Adaptive Icons for Android)

Adaptive icons were introduced in Android 8.0 (API level 26) to provide flexible app icons that can adapt to different device masks (e.g., circles, squares, squircles). This involves defining a foreground and background layer.

1. Create ic_launcher_foreground.xml in android/app/src/main/res/drawable/ (or mipmap-anydpi-v26):





2. Create ic_launcher_background.xml in android/app/src/main/res/drawable/ (or mipmap-anydpi-v26):





3. Update android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml and ic_launcher_round.xml (if they exist) to reference these new drawables:






You would also need to place your actual foreground image (e.g., my_app_icon_foreground.png) in the respective mipmap-xxxx folders.

Common Mistakes


1. Incorrect Icon Sizes/Densities: Not providing icons in all required sizes for iOS or all densities for Android leads to blurry or pixelated icons on some devices. Always use an icon generator or carefully follow platform guidelines for all asset sizes.
2. Forgetting pod install or react-native link (for older versions): After installing a native module like react-native-splash-screen, it's crucial to run cd ios && pod install && cd .. to link the native dependencies for iOS. Android often auto-links with React Native 0.60+, but manual steps might still be needed.
3. Hiding Splash Screen Too Early/Late: Hiding the splash screen immediately on component mount might still show a brief blank screen if your app has significant initial data fetching. Hiding it too late makes the app feel unresponsive. The best practice is to hide it after all critical initializations (e.g., authentication, first data load) are complete.

Best Practices


1. Use a Unified Design for Icon and Splash: Maintain consistent branding (colors, fonts, logo) between your app icon and splash screen to reinforce your brand identity.
2. Keep Splash Screens Simple: A splash screen should be visually appealing but not overly complex. Avoid animations that might make the app feel slower. A simple logo on a background color is often sufficient.
3. Optimize Splash Screen Assets: Use optimized image formats and appropriate resolutions to keep the splash screen file size small, ensuring it loads quickly.
4. Hide Splash Screen Programmatically: Always use SplashScreen.hide() (or equivalent) in your JavaScript code, triggered when your app's core components are ready, rather than relying on fixed timers. This ensures a seamless transition regardless of device performance or network speed.
5. Implement Adaptive Icons for Android: For Android 8.0 (API 26) and above, use adaptive icons to ensure your app icon looks great on all devices and launchers, adhering to modern Android design principles.

Practice Exercises


1. Icon Replacement: Find a generic image (e.g., a simple square with a letter) and replace your React Native project's default app icon on both iOS and Android using an online icon generator. Verify the change on a simulator/emulator.
2. Splash Screen Background Color Change: Modify your existing splash screen setup to change its background color from white to a distinct color (e.g., blue or green) on both iOS and Android. Rebuild and test.
3. Delayed Splash Screen Hide: In your App.js, introduce a setTimeout of 5 seconds before calling SplashScreen.hide(). Observe how the splash screen persists for longer. Then, revert it to hide immediately or based on a realistic loading condition.

Mini Project / Task


Create a new React Native project. Design a simple app icon (you can use text and a basic shape) and a corresponding splash screen that displays your app's name and logo. Implement the splash screen to remain visible for at least 2 seconds or until a simulated network request (using setTimeout) completes, whichever is longer. Ensure the app icon and splash screen work correctly on both iOS and Android emulators/simulators.

Challenge (Optional)


For Android, implement a fully adaptive icon. This means creating a foreground image (e.g., your app's logo without background) and a separate background layer (e.g., a solid color or gradient). Configure the Android project to use these adaptive icon components, ensuring it renders correctly with different mask shapes on an Android 8.0+ emulator.

Building for Android



Building React Native applications for the Android platform is a fundamental skill for any cross-platform mobile developer. Android, being the most widely used mobile operating system globally, offers an immense user base and diverse device ecosystem. React Native allows developers to write JavaScript code that compiles into native Android components, providing a seamless user experience that feels just like a purely native application. This section will guide you through the process of setting up your development environment, running your React Native app on an Android emulator or a physical device, and understanding the core concepts involved in the Android build process. We'll cover everything from initial setup to generating release builds for distribution, ensuring you have a solid foundation for deploying your React Native applications to the Android ecosystem. Understanding the Android build process is crucial for debugging, performance optimization, and successfully publishing your app to the Google Play Store.

While React Native handles much of the complexity, familiarity with Android's build tooling, like Gradle, and understanding Android Studio's role, greatly enhances your development experience. We'll explore how React Native bridges JavaScript with Java/Kotlin code, allowing access to native device features. This cross-platform approach significantly reduces development time and cost compared to maintaining separate codebases for Android and iOS, making it a highly attractive option for businesses and individual developers alike.

Step-by-Step Explanation


Let's walk through the process of setting up your environment and running your React Native app on Android.

1. Install Android Studio: Download and install Android Studio from the official Android developer website. This includes the Android SDK, Android Virtual Device (AVD) manager, and other essential tools. During installation, ensure you select 'Android Virtual Device' and 'Android SDK Platform' components.

2. Configure Android SDK Environment Variables: After installation, you need to set up environment variables. Open your ~/.bash_profile, ~/.zshrc, or ~/.bashrc file and add the following:
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools

Replace $HOME/Library/Android/sdk with your actual SDK path if it's different. Save the file and run source ~/.bash_profile (or your respective file).

3. Create a New React Native Project: If you haven't already, create a new React Native project using the Expo CLI or React Native CLI. For Android development, React Native CLI is often preferred for more control.
npx react-native init MyAwesomeApp
cd MyAwesomeApp

4. Start an Android Emulator or Connect a Physical Device:
- Emulator: Open Android Studio, go to 'Tools' -> 'AVD Manager', and create a new virtual device. Start the emulator from here.
- Physical Device: Enable USB Debugging on your Android device (usually in Developer Options, which you can unlock by tapping the build number in 'About Phone' multiple times). Connect your device to your computer via USB. Run adb devices in your terminal to ensure your device is recognized.

5. Run Your React Native App: With an emulator running or a device connected, navigate to your project directory in the terminal and run:
npx react-native run-android

This command builds the Android app and installs it on the running emulator or connected device.

Comprehensive Code Examples


Basic example: Running the default app
After creating a new project and setting up your environment, the default app will display 'Welcome to React Native!'.
// App.js (default generated file)
import React from 'react';
import {
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
useColorScheme,
View,
} from 'react-native';

import {
Colors,
DebugInstructions,
Header,
LearnMoreLinks,
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';

const Section = ({children, title}) => {
const isDarkMode = useColorScheme() === 'dark';
return (

style={ [
styles.sectionTitle,
{
color: isDarkMode ? Colors.white : Colors.black,
},
]}>
{title}

style={ [
styles.sectionDescription,
{
color: isDarkMode ? Colors.light : Colors.dark,
},
]}>
{children}


);
};

const App = () => {
const isDarkMode = useColorScheme() === 'dark';

const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};

return (

barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
contentInsetAdjustmentBehavior="automatic"
style={backgroundStyle}>

style={{
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>

Edit App.js to change this
screen and then come back to see your edits.








Read the docs to discover what to do next:





);
};

const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
},
highlight: {
fontWeight: '700',
},
});

export default App;


Real-world example: Customizing Android Manifest
The AndroidManifest.xml file is crucial for configuring your Android application. You can find it at android/app/src/main/AndroidManifest.xml. Here's how you might add permissions or change the app's theme.

package="com.myawesomeapp">




android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme">
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">








Advanced usage: Generating a Signed APK for Release
To publish your app to the Google Play Store, you need a signed APK or AAB (Android App Bundle).
1. Generate a keystore:
keytool -genkeypair -v -keystore my-upload-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000

2. Place my-upload-key.keystore in your android/app directory.
3. Add signing configurations to android/gradle.properties and android/app/build.gradle. (Details can be found in React Native docs).
4. Generate the release build:
cd android
./gradlew bundleRelease
./gradlew assembleRelease

The generated AAB/APK will be in android/app/build/outputs/bundle/release/app-release.aab or android/app/build/outputs/apk/release/app-release.apk.

Common Mistakes



  • Not setting ANDROID_HOME correctly: Many build failures stem from the system not finding the Android SDK. Double-check your environment variables and ensure the path is correct. Fix: Verify the path to your SDK in Android Studio (File -> Project Structure -> SDK Location) and update your shell config.

  • Running npx react-native run-android without an active emulator/device: The command will hang or fail if it can't find a target. Fix: Start an emulator via AVD Manager or ensure your physical device is connected and USB debugging is enabled, then run adb devices to confirm.

  • Gradle Sync Issues in Android Studio: Sometimes, opening the android folder in Android Studio might show Gradle errors. This often happens due to outdated Gradle versions or network issues. Fix: Try 'File' -> 'Sync Project with Gradle Files' in Android Studio. Ensure your internet connection is stable. Update Gradle wrapper if necessary (refer to React Native upgrade helper).


Best Practices



  • Keep Android Studio and SDK Tools Updated: Regularly update Android Studio and the SDK components via the SDK Manager to benefit from the latest features, bug fixes, and compatibility improvements.

  • Optimize for Performance: Pay attention to performance on Android devices, especially older ones. Use React Native's performance tools, avoid unnecessary re-renders, and use native modules for computationally intensive tasks.

  • Test on Multiple Devices/Emulators: Android's fragmentation means your app needs to work across various screen sizes, Android versions, and hardware specifications. Test thoroughly on a range of devices.

  • Understand Android Permissions: Explicitly declare and request necessary permissions in AndroidManifest.xml and at runtime for sensitive operations (e.g., camera, location).

  • Use Android App Bundles (AABs): When publishing to Google Play, always prefer AABs over APKs. AABs allow Google Play to generate and serve optimized APKs for each user's device configuration, resulting in smaller app downloads.


Practice Exercises



  • Exercise 1: Basic App Run
    Create a new React Native project. Ensure your Android development environment is correctly set up. Run the default React Native app on an Android emulator or a physical device. Take a screenshot of the running app.

  • Exercise 2: Custom Text Display
    Modify the App.js file in your React Native project to display a simple 'Hello Android World!' message in the center of the screen. Run the updated app on your Android emulator/device to verify the change.

  • Exercise 3: Add a Native Permission
    Edit the AndroidManifest.xml file in your React Native project to add the READ_CONTACTS permission. After adding it, rebuild and run your app on Android. (Note: This exercise only adds the permission; you don't need to implement contact reading functionality).


Mini Project / Task


Build a simple React Native application that displays the current battery level of the Android device. You will need to research how to create a native module for Android to access device battery information and then expose it to your React Native JavaScript code. Display the battery percentage on the screen.

Challenge (Optional)


Create a React Native app that allows the user to take a photo using the device's camera. Implement the functionality to save the taken photo to the device's gallery and then display a thumbnail of the image within your app. This will involve handling camera permissions, interacting with native camera APIs (potentially through a library like react-native-image-picker), and managing file storage on Android. Consider how your app handles different Android versions and permission models.

Building for iOS


Building for iOS with React Native involves configuring your development environment, compiling your JavaScript code into a native bundle, and running it on an iOS simulator or a physical device. This process leverages Apple's Xcode IDE, which is the primary tool for iOS development. React Native exists to allow developers to use their JavaScript and React knowledge to create truly native mobile applications, providing a write-once, run-anywhere experience for the UI layer while still accessing native device capabilities. It's widely used in real-world applications by companies like Facebook, Instagram, and Discord, enabling faster development cycles and code reuse across platforms. Understanding the iOS build process is crucial for debugging, deploying, and ensuring your app performs optimally on Apple devices.

Building for iOS encompasses several key steps: setting up the development environment, running on a simulator, and deploying to a physical device. Each of these stages requires specific tools and configurations. The core idea is that your JavaScript code is bundled and then executed by a JavaScript engine within a native iOS shell. This shell, managed by Xcode, handles all the native interactions, while React Native bridges the gap between your JavaScript components and the corresponding native UI elements.

Step-by-Step Explanation


To build for iOS, you'll first need to ensure you have a macOS machine, as Xcode is required and only runs on macOS. Install Xcode from the Mac App Store. After installation, open Xcode and let it install any additional components it prompts for. This is a critical first step. Next, ensure you have Node.js and Watchman installed. React Native CLI usually guides you through these, but manual installation via Homebrew is also an option (e.g., brew install node and brew install watchman).

Once your environment is set up, navigate to your React Native project directory in your terminal. To run your app on an iOS simulator, you can simply use the command npx react-native run-ios. This command will build your JavaScript bundle, compile the native iOS project using Xcode, and launch your app on the default iOS simulator. If you need to specify a particular simulator, you can use the --simulator flag, e.g., npx react-native run-ios --simulator="iPhone 15 Pro". For a physical device, you'll need an Apple Developer account, and you'll configure code signing in Xcode. Open the .xcworkspace file located in the ios directory of your project, then in Xcode, select your device, and click the 'Run' button.

Comprehensive Code Examples


Basic Example: Running on a Simulator

This is the most common way to start developing and testing your React Native app for iOS.
cd YourReactNativeProject
npx react-native run-ios

Real-world Example: Running on a Specific Simulator

When you need to test your app's layout or features on a particular iPhone model.
cd YourReactNativeProject
npx react-native run-ios --simulator="iPhone 15 Pro Max"

Advanced Usage: Building for Release and Archiving

For preparing your app for the App Store, you'll typically use Xcode directly. Open your project's .xcworkspace file, then go to 'Product' > 'Archive'. This creates an archive that can be uploaded to App Store Connect.
# This is a conceptual step, actual archiving is done via Xcode GUI
# 1. Open ios/YourProjectName.xcworkspace in Xcode.
# 2. Select 'Generic iOS Device' as the target.
# 3. Go to Product > Archive.
# 4. Follow the prompts to distribute your app.

Common Mistakes



  • Not having Xcode installed or updated: Xcode is essential. Always ensure it's installed and up-to-date. xcode-select --install can help with command-line tools.

  • Incorrect Node.js/Watchman setup: React Native relies heavily on these. Use nvm for Node.js version management and brew install watchman for Watchman.
  • Pod install issues: After adding new native modules, you must navigate to the ios directory and run pod install. For M1/M2 Macs, you might need arch -x86_64 pod install or bundle exec pod install if using Flipper or other specific setups.

Best Practices



  • Keep Xcode updated: New iOS versions often require the latest Xcode for full compatibility.

  • Use a consistent Node.js version: Manage Node.js versions with nvm to avoid conflicts.

  • Understand Pods: CocoaPods manages native dependencies. Always run pod install after adding new React Native libraries with native code.

  • Clean your build folder: If you encounter strange build errors, try cleaning your Xcode build folder (Product > Clean Build Folder) and then rebuilding.


Practice Exercises



  1. Set up a new React Native project and successfully run it on an iOS simulator.

  2. Experiment with running your app on at least two different iOS simulator devices (e.g., iPhone SE and iPhone 15 Pro Max).

  3. Modify your App.js to display a simple 'Hello, iOS!' text and verify the changes appear on the simulator.


Mini Project / Task


Create a new React Native project. Add a button that, when pressed, changes the background color of the entire screen. Ensure this functionality works correctly when running the app on an iOS simulator, and try it on at least two different simulator models to check for layout consistency.

Challenge (Optional)


Without using npx react-native run-ios, try to build and run your React Native app on an iOS simulator purely through Xcode. This involves opening the .xcworkspace file, selecting a simulator, and initiating the build process from within the Xcode IDE. Document the steps you take.

Publishing to App Stores

Publishing is the final step that turns a React Native project into a real product users can install from Google Play and the Apple App Store. It exists because mobile platforms require secure packaging, signing, metadata, screenshots, privacy declarations, and review before apps reach the public. In real projects, teams use this process to distribute shopping apps, banking tools, fitness trackers, internal enterprise apps, and startup products. In React Native, publishing involves two major release paths: Android and iOS. Android commonly uses an AAB file for Google Play, while iOS uses an archived release build uploaded through Xcode or Transporter. You must also understand versioning, signing keys, environment configuration, store listings, and production testing. For Android, you generate a keystore, configure Gradle signing, increase versionCode, and create a release bundle. For iOS, you set a unique bundle identifier, configure certificates and provisioning in Apple Developer, update the version and build number, archive the app, then submit it through App Store Connect. Store publishing also includes app icons, splash assets, privacy usage descriptions, permissions review, and release notes. If these pieces are missing or inconsistent, stores may reject the app. The goal is not only to upload a build, but to ship a stable, policy-compliant release that users can trust.

Step-by-Step Explanation

1. Prepare production settings. Set the app name, package name or bundle ID, icons, splash screen, API endpoints, and environment variables.
2. Update versions. On Android, edit versionCode and versionName in android/app/build.gradle. On iOS, update version and build in Xcode.
3. Configure signing. Android requires a keystore and Gradle signing config. iOS requires Apple certificates and provisioning profiles.
4. Build release artifacts. Android usually uses ./gradlew bundleRelease. iOS uses Archive in Xcode.
5. Test the release build on physical devices. Debug builds can hide production problems.
6. Create store listings with title, description, screenshots, privacy details, and age rating.
7. Upload and submit for review. Respond to warnings, policy checks, or review feedback.

Comprehensive Code Examples

Basic example: Android versioning

android {
defaultConfig {
applicationId "com.example.myapp"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 5
versionName "1.0.4"
}
}

Real-world example: Android signing config

android {
signingConfigs {
release {
storeFile file(MYAPP_UPLOAD_STORE_FILE)
storePassword MYAPP_UPLOAD_STORE_PASSWORD
keyAlias MYAPP_UPLOAD_KEY_ALIAS
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
shrinkResources true
}
}
}

Advanced usage: environment-aware release check

import {Alert} from 'react-native';

const config = {
apiBaseUrl: 'https://api.example.com',
appEnv: 'production',
};

export function validateReleaseConfig() {
if (config.appEnv !== 'production') {
Alert.alert('Warning', 'App is not using production configuration.');
}
}

Common Mistakes

  • Forgetting to increase build numbers, which causes upload failures. Fix: increment every release.

  • Using debug endpoints or test keys in production. Fix: verify environment variables before building.

  • Missing privacy permission messages on iOS. Fix: add all required usage descriptions in Info.plist.

  • Testing only debug builds. Fix: install and validate the actual release build on devices.

Best Practices

  • Store signing credentials securely and never commit secrets to version control.

  • Automate builds with CI/CD tools such as GitHub Actions, Bitrise, or Fastlane.

  • Keep release notes clear and user-focused.

  • Use internal testing tracks and TestFlight before public rollout.

  • Review platform policies regularly because store requirements change often.

Practice Exercises

  • Create a release checklist for Android and iOS that includes signing, versioning, testing, and store metadata.

  • Update a sample React Native project with a new versionCode, versionName, and production package identifier.

  • Write the Gradle release signing configuration using environment variables instead of hardcoded passwords.

Mini Project / Task

Prepare a React Native app for launch by configuring Android signing, generating an Android App Bundle, updating iOS version details, and creating a complete mock store listing with screenshots, privacy notes, and release text.

Challenge (Optional)

Design a repeatable release workflow that supports staging and production builds, automated version increments, internal testing distribution, and a rollback plan if a published update has critical issues.

Final Project


The final project in a React Native course is your opportunity to synthesize all the knowledge and skills you've acquired throughout the curriculum into a tangible, functional mobile application. It's designed to simulate real-world development scenarios, requiring you to plan, design, implement, and debug a complete application. This project serves as a capstone, demonstrating your proficiency in React Native components, state management, navigation, API integration, styling, and potentially much more. It's crucial for solidifying your understanding, building confidence, and creating a portfolio piece that showcases your abilities to potential employers or clients. In essence, it's where theoretical knowledge transforms into practical application.

The final project exists to provide a comprehensive assessment of your learning journey. It goes beyond isolated exercises by challenging you to connect various concepts and make design decisions, often involving trade-offs. In real-life mobile development, the ability to build a complete application from scratch, manage its complexity, and deliver a user-friendly experience is paramount. This project prepares you for such demands, whether you're aiming to develop your own apps, join a startup, or work for a large tech company. Many successful apps, from social media platforms to utility tools, are built using frameworks like React Native, and this project aims to give you a taste of that development process.

Step-by-Step Explanation


Undertaking a final project involves several key phases, much like a professional software development lifecycle. Here’s a breakdown:

1. Project Conception & Planning: Start by defining the application's core idea, its purpose, and key features. Think about the problem it solves or the value it provides. Create a basic wireframe or sketch of the user interface (UI) and user experience (UX). Identify the main screens and how users will navigate between them. Decide on the technologies you'll use beyond core React Native, such as specific navigation libraries (e.g., React Navigation), state management solutions (e.g., Redux, Zustand, Context API), and any backend services or APIs you might integrate.

2. Setup & Core Structure: Initialize your React Native project using Expo CLI or React Native CLI. Set up your project structure logically (e.g., `src/components`, `src/screens`, `src/navigation`, `src/utils`). Install necessary dependencies for navigation, styling, and any other core functionalities.

3. Component Development: Begin building individual UI components. Focus on reusability and modularity. Develop the basic layout for each screen. This phase often involves creating custom components like buttons, input fields, cards, or lists.

4. Navigation Implementation: Integrate a navigation library (e.g., React Navigation) and set up the flow between your different screens. Implement stack navigators for sequential screens, tab navigators for primary sections, and potentially drawer navigators for additional options.

5. State Management: Implement a state management solution to handle application data. This is crucial for managing global state, user authentication, or data fetched from an API. Choose a method that fits your project's complexity and your comfort level.

6. API Integration (if applicable): If your app requires dynamic data, integrate with a backend API. This involves making HTTP requests (using `fetch` or `axios`), handling responses, and displaying data in your UI. Ensure error handling for network requests.

7. Styling & Theming: Apply consistent styling to your application. This can involve using `StyleSheet`, styled-components, or a UI library. Consider implementing a basic theme for colors and typography.

8. Testing & Debugging: Thoroughly test your application on both iOS and Android emulators/simulators and, if possible, on physical devices. Identify and fix bugs. Pay attention to edge cases and user interactions.

9. Refinement & Polish: Improve the user experience by adding animations, transitions, or feedback mechanisms. Optimize performance. Ensure the UI is responsive and visually appealing.

10. Documentation & Presentation: Document your code where necessary, especially for complex logic. Prepare a brief presentation or README file explaining your project, its features, and the technologies used.

Comprehensive Code Examples


Given the scope of a final project, providing a single comprehensive code example is impractical. Instead, here are illustrative snippets for common project elements.

Basic Example: Setting up a Tab Navigator
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { NavigationContainer } from '@react-navigation/native';
import HomeScreen from './screens/HomeScreen';
import SettingsScreen from './screens/SettingsScreen';

const Tab = createBottomTabNavigator();

function AppTabs() {
return (




);
}

export default function App() {
return (



);
}


Real-world Example: Fetching data from an API
import React, { useState, useEffect } from 'react';
import { View, Text, FlatList, ActivityIndicator, StyleSheet } from 'react-native';

const ProductListScreen = () => {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
const fetchProducts = async () => {
try {
const response = await fetch('https://dummyjson.com/products');
if (!response.ok) {
throw new Error('Failed to fetch products');
}
const data = await response.json();
setProducts(data.products);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};

fetchProducts();
}, []);

if (loading) {
return (


Loading products...

);
}

if (error) {
return (

Error: {error}

);
}

return (

data={products}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (

{item.title}
Price: ${item.price}

)}
/>

);
};

const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 22,
},
center: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
productItem: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
productTitle: {
fontSize: 18,
fontWeight: 'bold',
},
errorText: {
color: 'red',
fontSize: 16,
},
});

export default ProductListScreen;


Advanced Usage: Custom Hook for Form Management
import { useState } from 'react';

const useForm = (initialState, validate) => {
const [values, setValues] = useState(initialState);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);

const handleChange = (name, value) => {
setValues({
...values,
[name]: value,
});
};

const handleSubmit = (callback) => {
setIsSubmitting(true);
const validationErrors = validate(values);
setErrors(validationErrors);

if (Object.keys(validationErrors).length === 0) {
callback();
} else {
setIsSubmitting(false);
}
};

return {
values,
errors,
isSubmitting,
handleChange,
handleSubmit,
setValues, // Useful for resetting form
};
};

export default useForm;


Common Mistakes



  • Inadequate Planning: Jumping straight into coding without a clear plan, wireframes, or feature list often leads to disorganized code, scope creep, and a difficult development process.
    Fix: Dedicate time to planning. Sketch out UI, list features, and consider data flow before writing any code. Use tools like Figma or even pen and paper.

  • Ignoring Error Handling: Not anticipating network failures, invalid user input, or unexpected API responses can crash your app or lead to a poor user experience.
    Fix: Implement `try-catch` blocks for asynchronous operations, validate all user inputs, and provide clear feedback to the user when something goes wrong.

  • Poor Component Organization/Reusability: Creating large, monolithic components or duplicating code for similar UI elements makes the project hard to maintain and scale.
    Fix: Break down UI into small, reusable components. Use props effectively to customize components and avoid direct state manipulation in parent components where possible.



Best Practices



  • Modularize Your Code: Organize your project into logical directories and files (e.g., `components`, `screens`, `navigation`, `utils`, `api`). This improves readability, maintainability, and scalability.

  • Consistent Styling: Use `StyleSheet.create` for performance and organization. Consider using a design system or theming solution to maintain visual consistency across your app.

  • Optimize Performance: Use `FlatList` or `SectionList` for displaying long lists of data. Avoid unnecessary re-renders with `React.memo` and `useCallback`/`useMemo`. Minimize large images and complex animations where possible.

  • Utilize Version Control: Use Git and GitHub/GitLab from day one. Commit frequently with descriptive messages. This helps track changes, revert mistakes, and collaborate if you're working in a team.

  • Test on Multiple Devices/Platforms: Always test on both iOS and Android emulators/simulators. If possible, test on physical devices to catch platform-specific issues and performance quirks.



Practice Exercises



  • User Profile Screen: Create a static 'User Profile' screen with an image, name, email, and a few editable text fields. Implement basic local state to handle changes in these fields.

  • Simple To-Do List: Build a To-Do list application that allows users to add new tasks, mark tasks as complete, and delete tasks. Manage the list of tasks using component state.

  • Navigation Flow: Set up a basic app with three screens: 'Home', 'About', and 'Contact'. Implement a Stack Navigator to move between them and a Tab Navigator at the bottom for quick access to 'Home' and 'About'.



Mini Project / Task


Design and implement a simple 'Weather App' that fetches current weather data for a hardcoded city (e.g., 'London') from a public weather API (e.g., OpenWeatherMap, make sure to get an API key). Display the city name, temperature, and a brief weather description. Include a loading indicator while fetching data and an error message if the API call fails.

Challenge (Optional)


Extend the 'Weather App' mini-project. Allow the user to input any city name to fetch its weather data. Implement a search bar for city input and save the last 3 searched cities in local storage (using `AsyncStorage`) to display them as recent searches on the home screen. Also, add a toggle to switch between Celsius and Fahrenheit.

Get a Free Quote!

Fill out the form below and we'll get back to you shortly.

(Minimum characters 0 of 100)

Illustration

Fast Response

Get a quote within 24 hours

💰

Best Prices

Competitive rates guaranteed

No Obligation

Free quote with no commitment