React Native is Meta's open-source framework for building truly native mobile applications for iOS and Android using JavaScript and React. Rather than rendering web views, React Native translates your React components into real native platform widgets — delivering the performance and feel of a native app with the productivity of React's declarative UI model.
This guide covers everything you need to go from zero to a production-ready React Native app: installation, configuration, core concepts, common tasks, and troubleshooting for real-world issues.
Note
This guide covers React Native 0.86.x on the New Architecture (Fabric renderer, TurboModules, Codegen). The New Architecture is the default for all new projects. If you need details on internals, architecture decisions, or deployment infrastructure, see the Admin Guide (Admin section).
Table of Contents
- 1. Quick Start
- 2. Installation
- 3. Configuration
- 4. Core Usage
- 5. Common Tasks
- 6. Troubleshooting
- 7. FAQ
1. Quick Start
Get a new React Native app running on a simulator or device in under five minutes.
1.1 Prerequisites at a Glance
| Requirement | Version |
|---|---|
| Node.js | ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0 |
| Ruby (iOS only) | >= 2.6.10 |
| Xcode (iOS) | Latest stable |
| Android Studio (Android) | Latest stable with SDK 35+ |
1.2 Create and Run Your First App
npx @react-native-community/cli@latest init MyFirstApp
cd MyFirstApp
Run on iOS:
npx react-native start
# In a new terminal:
npx react-native run-ios
Run on Android (ensure an emulator is running or a device is connected):
npx react-native start
# In a new terminal:
npx react-native run-android
Expected output from npx react-native start:
info Starting dev server on port 8081...
▐▀▄ ▄▀▌
▐ ▀▄▄▄▄▄▀ ▌
▐ ▌
▐ ▌
▐ ▄ ▌
▐ ▐ ▌ ▌
▐ ▀ ▀ ▌
Welcome to React Native 0.86
Dev server ready.
1.3 Edit Your App
Open App.tsx in your editor. Make a change, save, and watch it appear instantly on the device — Fast Refresh is enabled by default.
import React from 'react';
import {SafeAreaView, Text, StyleSheet} from 'react-native';
function App(): React.JSX.Element {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>Hello, React Native!</Text>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
},
});
export default App;
Tip
Shake your device (or press
Cmd+Don iOS Simulator /Cmd+Mon Android Emulator) to open the Dev Menu. From there you can toggle the inspector, reload the app, or open React Native DevTools.
2. Installation
2.1 System Requirements
2.1.1 All Platforms
| Tool | Required Version | Purpose |
|---|---|---|
| Node.js | ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0 |
JavaScript runtime |
| npm / Yarn | Bundled with Node / latest | Package management |
| Watchman | Latest (recommended) | File watching for Fast Refresh |
| Git | Latest | Source control |
2.1.2 iOS Development (macOS Only)
| Tool | Required Version | Purpose |
|---|---|---|
| macOS | Latest or previous major | Host OS for iOS builds |
| Xcode | Latest stable | iOS SDK, simulator, build tools |
| Xcode Command Line Tools | Matching Xcode | xcode-select --install |
| Ruby | >= 2.6.10 | CocoaPods dependency manager |
| CocoaPods | Latest | iOS native dependency management |
2.1.3 Android Development
| Tool | Required Version | Purpose |
|---|---|---|
| Android Studio | Latest stable | Android SDK, emulator, build tools |
| Android SDK | API 35+ | Target platform |
| Android SDK Build-Tools | Latest | Compilation |
| Android NDK | Required for native modules | C++ compilation |
| Java Development Kit | JDK 17 | Gradle builds |
2.2 macOS Setup
2.2.1 Install Homebrew Dependencies
brew install node watchman
Verify Node version:
node --version
# Expected: v22.x.x or v20.x.x (matching required range)
2.2.2 Install Ruby and CocoaPods (iOS)
macOS ships with Ruby, but using a version manager is recommended:
brew install rbenv ruby-build
rbenv install 3.3.0
rbenv global 3.3.0
# Verify
ruby --version
# Expected: ruby 3.3.0 or higher
gem install cocoapods
2.2.3 Install Xcode (iOS)
- Install Xcode from the Mac App Store
- Open Xcode and accept the license agreement
- Install Command Line Tools:
xcode-select --install
- Verify the installation:
xcodebuild -version
# Expected: Xcode 16.x, Build version ...
2.2.4 Install Android Studio (Android)
- Download Android Studio from https://developer.android.com/studio
- During installation, ensure these are checked:
- Android SDK
- Android SDK Platform
- Android Virtual Device
- Open Android Studio > Settings > SDK Manager
- Install SDK Platform for your target API level (35+)
- Install NDK (Side by side) under SDK Tools tab
2.2.5 Configure Environment Variables
Add to your ~/.zshrc or ~/.bash_profile:
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
Reload your shell:
source ~/.zshrc
2.3 Windows Setup (Android Only)
Important
iOS development requires macOS. On Windows, you can only build for Android. Use a Mac or a cloud-based Mac service for iOS builds.
2.3.1 Install Node.js
Download the LTS installer from https://nodejs.org/ or use a version manager:
# Using Chocolatey
choco install nodejs-lts
# Verify
node --version
2.3.2 Install Android Studio
- Download from https://developer.android.com/studio
- During install, check: Android SDK, Android SDK Platform, Android Virtual Device
- Install SDK Platform API 35+ from SDK Manager
- Install NDK from SDK Tools tab
2.3.3 Configure Environment Variables
Set the following system environment variables:
| Variable | Value |
|---|---|
ANDROID_HOME |
C:\Users\<user>\AppData\Local\Android\Sdk |
Add to Path |
%ANDROID_HOME%\emulator |
Add to Path |
%ANDROID_HOME%\platform-tools |
2.4 Linux Setup (Android Only)
# Install Node.js (using NodeSource)
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install Watchman
sudo apt-get install watchman
# Install JDK 17
sudo apt-get install openjdk-17-jdk
Then install Android Studio as described in section 2.3.2.
2.5 Creating a New Project
2.5.1 Using npx (Recommended)
npx @react-native-community/cli@latest init MyProject
Expected output:
✔ Downloading template
✔ Copying template
✔ Processing template
✔ Installing dependencies
✔ Installing CocoaPods dependencies (iOS)
Run instructions for iOS:
npx react-native run-ios
Run instructions for Android:
npx react-native run-android
2.5.2 With a Specific Version
npx @react-native-community/cli@latest init MyProject --version 0.86.0
2.5.3 With a Custom Template
npx @react-native-community/cli@latest init MyProject --template react-native-template-typescript
2.6 Project Structure
After initialization, your project will have this structure:
MyProject/
├── __tests__/ # Jest test files
├── android/ # Android native project
│ ├── app/
│ │ ├── build.gradle
│ │ └── src/
│ ├── build.gradle
│ ├── gradle.properties
│ └── settings.gradle
├── ios/ # iOS native project
│ ├── MyProject/
│ ├── Podfile
│ └── MyProject.xcworkspace
├── node_modules/
├── App.tsx # Root component
├── app.json # App configuration
├── babel.config.js # Babel configuration
├── index.js # App entry point
├── metro.config.js # Metro bundler configuration
├── package.json
└── tsconfig.json # TypeScript configuration
2.7 Verifying Your Setup
Run the built-in environment check:
npx react-native doctor
This checks for Node, Watchman, Xcode, CocoaPods, Android Studio, Android SDK, and JDK. Fix any issues it reports before proceeding.
3. Configuration
3.1 Metro Bundler Configuration
Metro is React Native's JavaScript bundler. The default configuration is provided by @react-native/metro-config.
3.1.1 Default Configuration
Your project's metro.config.js:
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const config = {};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
The default configuration includes:
| Setting | Default Value | Purpose |
|---|---|---|
| Resolver fields | ['react-native', 'browser', 'main'] |
Module resolution order |
| Platforms | ['android', 'ios'] |
Supported target platforms |
| Inline requires | true |
Performance optimization |
| Port | 8081 |
Dev server port |
3.1.2 Changing the Dev Server Port
Set the RCT_METRO_PORT environment variable:
RCT_METRO_PORT=8088 npx react-native start
Or configure it in metro.config.js:
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const config = {
server: {
port: 8088,
},
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
3.1.3 Adding Asset Extensions
To support additional asset types beyond the defaults:
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
const config = {
resolver: {
assetExts: [...defaultConfig.resolver.assetExts, 'obj', 'mtl', 'gltf'],
},
};
module.exports = mergeConfig(defaultConfig, config);
3.1.4 Adding Source Extensions
To support additional source file extensions:
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const defaultConfig = getDefaultConfig(__dirname);
const config = {
resolver: {
sourceExts: [...defaultConfig.resolver.sourceExts, 'cjs', 'mjs'],
},
};
module.exports = mergeConfig(defaultConfig, config);
3.1.5 Configuring Module Aliases
const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const path = require('path');
const config = {
resolver: {
extraNodeModules: {
'@app': path.resolve(__dirname, 'src'),
'@components': path.resolve(__dirname, 'src/components'),
'@utils': path.resolve(__dirname, 'src/utils'),
},
},
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
3.2 Babel Configuration
React Native uses @react-native/babel-preset which includes support for Flow stripping, class properties, async/await, optional chaining, nullish coalescing, and more.
3.2.1 Default Configuration
Your babel.config.js:
module.exports = {
presets: ['module:@react-native/babel-preset'],
};
3.2.2 Adding Plugins
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
'react-native-reanimated/plugin', // Must be last
],
};
Warning
If you use
react-native-reanimated, its Babel plugin must be listed last in thepluginsarray. Placing it elsewhere causes subtle runtime errors that are difficult to diagnose.
3.2.3 Environment-Specific Configuration
module.exports = (api) => {
const isProd = api.env('production');
return {
presets: ['module:@react-native/babel-preset'],
plugins: [
isProd && 'transform-remove-console',
].filter(Boolean),
};
};
3.3 TypeScript Configuration
Default tsconfig.json:
{
"extends": "@react-native/typescript-config/tsconfig.json",
"compilerOptions": {
"strict": true
}
}
3.3.1 Path Aliases with TypeScript
If you configure Metro module aliases (section 3.1.5), mirror them in tsconfig.json:
{
"extends": "@react-native/typescript-config/tsconfig.json",
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"paths": {
"@app/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
}
}
3.4 ESLint Configuration
React Native provides @react-native/eslint-config and @react-native/eslint-plugin for code quality.
3.4.1 Default Setup
Your .eslintrc.js:
module.exports = {
root: true,
extends: '@react-native',
};
This extends @react-native/eslint-config, which bundles rules for React, React Native, Jest, Flow/TypeScript, and Prettier.
3.4.2 Adding Custom Rules
module.exports = {
root: true,
extends: '@react-native',
rules: {
'no-console': 'warn',
'react-native/no-inline-styles': 'warn',
'react/no-unstable-nested-components': 'error',
},
};
3.5 Jest Configuration
React Native uses @react-native/jest-preset for testing.
3.5.1 Default Setup
In your package.json:
{
"jest": {
"preset": "react-native"
}
}
Or in a standalone jest.config.js:
module.exports = {
preset: 'react-native',
setupFilesAfterSetup: ['./jest.setup.js'],
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|@react-navigation)/)',
],
};
3.5.2 Module Name Mapping
If you use path aliases, configure Jest to resolve them:
module.exports = {
preset: 'react-native',
moduleNameMapper: {
'^@app/(.*)$': '<rootDir>/src/$1',
'^@components/(.*)$': '<rootDir>/src/components/$1',
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
},
};
3.6 Prettier Configuration
React Native uses these Prettier settings internally:
{
"arrowParens": "avoid",
"singleQuote": true,
"trailingComma": "all"
}
Create a .prettierrc in your project root with these or your preferred settings to maintain consistent formatting.
3.7 Android Configuration
3.7.1 Gradle Plugin
React Native's Gradle plugin is applied in your Android project. The key plugins are:
com.facebook.react— applied to yourapp/build.gradlecom.facebook.react.rootproject— applied to the rootbuild.gradle
3.7.2 Gradle Properties
Key properties in android/gradle.properties:
# Enable the New Architecture (default: true in 0.86)
newArchEnabled=true
# Enable Hermes JS engine (default: true)
hermesEnabled=true
# Configure build variants
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
3.7.3 App-Level build.gradle
apply plugin: "com.facebook.react"
react {
// Configures bundling for release builds
// entryFile = file("index.js")
// bundleCommand = "bundle"
}
android {
namespace "com.myproject"
compileSdk 35
defaultConfig {
applicationId "com.myproject"
minSdk 24
targetSdk 35
versionCode 1
versionName "1.0"
}
}
3.8 iOS Configuration
3.8.1 Podfile
The iOS Podfile manages native dependencies via CocoaPods:
platform :ios, '15.1'
target 'MyProject' do
config = use_native_modules!
use_react_native!(
:path => config[:reactNativePath],
:hermes_enabled => true,
:fabric_enabled => true,
)
post_install do |installer|
react_native_post_install(installer)
end
end
3.8.2 Installing iOS Dependencies
cd ios
bundle install # Install Ruby gems (if using Bundler)
pod install # Install CocoaPods dependencies
cd ..
Expected output:
Analyzing dependencies
Downloading dependencies
Installing React-Core (0.86.0)
Installing hermes-engine (0.86.0)
...
Pod installation complete!
3.9 App Configuration (app.json)
The app.json file provides basic app metadata:
{
"name": "MyProject",
"displayName": "My Project"
}
This is read by index.js to register the app:
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
4. Core Usage
4.1 Components
React Native provides a set of core components that map directly to native platform views.
4.1.1 Basic Components
| Component | Purpose | Native Equivalent |
|---|---|---|
View |
Container, layout | UIView (iOS), ViewGroup (Android) |
Text |
Display text | UILabel (iOS), TextView (Android) |
Image |
Display images | UIImageView (iOS), ImageView (Android) |
TextInput |
Text entry | UITextField (iOS), EditText (Android) |
ScrollView |
Scrollable container | UIScrollView (iOS), ScrollView (Android) |
Button |
Basic button | Native button |
import React from 'react';
import {View, Text, Image, TextInput, Button, ScrollView} from 'react-native';
function BasicComponents(): React.JSX.Element {
return (
<ScrollView>
<View style={{padding: 20}}>
<Text>Hello, world!</Text>
<Image
source={{uri: 'https://reactnative.dev/img/tiny_logo.png'}}
style={{width: 50, height: 50}}
/>
<TextInput placeholder="Type here..." />
<Button title="Press Me" onPress={() => console.log('Pressed!')} />
</View>
</ScrollView>
);
}
4.1.2 Layout with View and Flexbox
React Native uses Flexbox for layout. The default flexDirection is column (unlike web CSS which defaults to row).
import React from 'react';
import {View, Text, StyleSheet} from 'react-native';
function FlexboxExample(): React.JSX.Element {
return (
<View style={styles.container}>
<View style={styles.row}>
<View style={[styles.box, {backgroundColor: '#e74c3c'}]}>
<Text style={styles.label}>1</Text>
</View>
<View style={[styles.box, {backgroundColor: '#3498db'}]}>
<Text style={styles.label}>2</Text>
</View>
<View style={[styles.box, {backgroundColor: '#2ecc71'}]}>
<Text style={styles.label}>3</Text>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 20,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
},
box: {
width: 80,
height: 80,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
},
label: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
},
});
Key Flexbox properties:
| Property | Values | Default |
|---|---|---|
flexDirection |
column, row, column-reverse, row-reverse |
column |
justifyContent |
flex-start, flex-end, center, space-between, space-around, space-evenly |
flex-start |
alignItems |
flex-start, flex-end, center, stretch, baseline |
stretch |
flex |
Number | — |
flexWrap |
wrap, nowrap |
nowrap |
gap |
Number | — |
rowGap |
Number | — |
columnGap |
Number | — |
4.1.3 Text and Typography
import React from 'react';
import {Text, StyleSheet} from 'react-native';
function Typography(): React.JSX.Element {
return (
<>
<Text style={styles.heading}>Heading</Text>
<Text style={styles.body}>
Body text with <Text style={styles.bold}>bold</Text> and{' '}
<Text style={styles.italic}>italic</Text> spans.
</Text>
<Text style={styles.link} onPress={() => console.log('Tapped!')}>
Tappable text
</Text>
<Text numberOfLines={2} ellipsizeMode="tail">
This is a very long paragraph that will be truncated after two lines
because we set numberOfLines to 2 and ellipsizeMode to tail so it
shows an ellipsis at the end.
</Text>
</>
);
}
const styles = StyleSheet.create({
heading: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 8,
},
body: {
fontSize: 16,
lineHeight: 24,
color: '#333',
},
bold: {
fontWeight: 'bold',
},
italic: {
fontStyle: 'italic',
},
link: {
color: '#3498db',
textDecorationLine: 'underline',
},
});
4.1.4 Images
import React from 'react';
import {Image, ImageBackground, StyleSheet, Text, View} from 'react-native';
function ImageExamples(): React.JSX.Element {
return (
<View>
{/* Local image (require) */}
<Image source={require('./assets/logo.png')} style={styles.localImage} />
{/* Remote image (must specify dimensions) */}
<Image
source={{uri: 'https://example.com/photo.jpg'}}
style={styles.remoteImage}
resizeMode="cover"
/>
{/* Background image */}
<ImageBackground
source={require('./assets/background.png')}
style={styles.background}>
<Text style={styles.overlayText}>Overlay text</Text>
</ImageBackground>
</View>
);
}
const styles = StyleSheet.create({
localImage: {
width: 100,
height: 100,
},
remoteImage: {
width: 200,
height: 150,
borderRadius: 8,
},
background: {
width: '100%',
height: 200,
justifyContent: 'center',
alignItems: 'center',
},
overlayText: {
color: 'white',
fontSize: 24,
fontWeight: 'bold',
},
});
Image resizeMode options:
| Mode | Behavior |
|---|---|
cover |
Scale to fill, may crop |
contain |
Scale to fit, may letterbox |
stretch |
Stretch to fill exactly |
repeat |
Tile the image (iOS only) |
center |
Center without scaling |
4.1.5 TextInput
import React, {useState} from 'react';
import {TextInput, View, Text, StyleSheet} from 'react-native';
function FormExample(): React.JSX.Element {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [bio, setBio] = useState('');
return (
<View style={styles.form}>
<Text style={styles.label}>Name</Text>
<TextInput
style={styles.input}
value={name}
onChangeText={setName}
placeholder="Your name"
autoCapitalize="words"
returnKeyType="next"
/>
<Text style={styles.label}>Email</Text>
<TextInput
style={styles.input}
value={email}
onChangeText={setEmail}
placeholder="[email removed]"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
returnKeyType="next"
/>
<Text style={styles.label}>Bio</Text>
<TextInput
style={[styles.input, styles.multiline]}
value={bio}
onChangeText={setBio}
placeholder="Tell us about yourself..."
multiline
numberOfLines={4}
textAlignVertical="top"
/>
</View>
);
}
const styles = StyleSheet.create({
form: {
padding: 20,
},
label: {
fontSize: 14,
fontWeight: '600',
marginBottom: 4,
color: '#333',
},
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
marginBottom: 16,
fontSize: 16,
},
multiline: {
height: 100,
},
});
Keyboard types available via keyboardType:
| Value | Use Case |
|---|---|
default |
Standard text |
email-address |
Email input |
numeric |
Numbers only |
phone-pad |
Phone number |
decimal-pad |
Decimal numbers |
number-pad |
Integer numbers |
url |
URL input |
4.2 Styling
4.2.1 StyleSheet API
Always use StyleSheet.create() for performance — it validates styles at creation time and creates optimized references:
import {StyleSheet} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#1a1a1a',
marginBottom: 12,
},
});
4.2.2 Combining Styles
// Array syntax — later styles override earlier ones
<View style={[styles.base, styles.active]} />
// Conditional styles
<View style={[styles.base, isActive && styles.active]} />
// Inline overrides
<View style={[styles.base, {marginTop: 10}]} />
4.2.3 Platform-Specific Styles
import {Platform, StyleSheet} from 'react-native';
const styles = StyleSheet.create({
shadow: {
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: {width: 0, height: 2},
shadowOpacity: 0.25,
shadowRadius: 4,
},
android: {
elevation: 5,
},
}),
},
});
4.2.4 Common Style Properties
React Native styles are a subset of CSS, written in camelCase. Key differences from web CSS:
| Category | Properties |
|---|---|
| Layout | flex, flexDirection, justifyContent, alignItems, alignSelf, position, top, left, right, bottom, width, height, minWidth, maxWidth, minHeight, maxHeight, margin*, padding*, gap |
| Visual | backgroundColor, opacity, borderWidth, borderColor, borderRadius, borderStyle |
| Text | color, fontSize, fontWeight, fontStyle, fontFamily, lineHeight, textAlign, textDecorationLine, textTransform, letterSpacing |
| Shadow (iOS) | shadowColor, shadowOffset, shadowOpacity, shadowRadius |
| Shadow (Android) | elevation |
| Transform | transform: [{rotate: '45deg'}, {scale: 1.5}] |
Note
All dimensions in React Native are unitless and represent density-independent pixels (dp). There is no
px,em,rem, or%unit (except for percentage strings like'50%'for width/height). This ensures consistent sizing across different screen densities.
4.3 Lists and Scrolling
4.3.1 FlatList — Virtualized List
FlatList is the primary component for rendering long, scrollable lists. It only renders items currently visible on screen, making it efficient for large datasets.
import React from 'react';
import {FlatList, Text, View, StyleSheet} from 'react-native';
interface Item {
id: string;
title: string;
}
const DATA: Item[] = Array.from({length: 1000}, (_, i) => ({
id: String(i),
title: `Item ${i + 1}`,
}));
function ListExample(): React.JSX.Element {
const renderItem = ({item}: {item: Item}) => (
<View style={styles.item}>
<Text style={styles.itemText}>{item.title}</Text>
</View>
);
return (
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListHeaderComponent={() => (
<Text style={styles.header}>All Items</Text>
)}
ListEmptyComponent={() => (
<Text style={styles.empty}>No items found</Text>
)}
/>
);
}
const styles = StyleSheet.create({
item: {
padding: 16,
backgroundColor: '#fff',
},
itemText: {
fontSize: 16,
},
separator: {
height: 1,
backgroundColor: '#eee',
},
header: {
fontSize: 20,
fontWeight: 'bold',
padding: 16,
},
empty: {
padding: 20,
textAlign: 'center',
color: '#999',
},
});
4.3.2 SectionList — Grouped Data
import React from 'react';
import {SectionList, Text, View, StyleSheet} from 'react-native';
const SECTIONS = [
{
title: 'Fruits',
data: ['Apple', 'Banana', 'Cherry', 'Date'],
},
{
title: 'Vegetables',
data: ['Artichoke', 'Broccoli', 'Carrot', 'Daikon'],
},
];
function SectionListExample(): React.JSX.Element {
return (
<SectionList
sections={SECTIONS}
keyExtractor={(item, index) => item + index}
renderItem={({item}) => (
<View style={styles.item}>
<Text>{item}</Text>
</View>
)}
renderSectionHeader={({section: {title}}) => (
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>{title}</Text>
</View>
)}
stickySectionHeadersEnabled
/>
);
}
const styles = StyleSheet.create({
item: {
padding: 16,
backgroundColor: '#fff',
},
sectionHeader: {
padding: 12,
backgroundColor: '#f5f5f5',
},
sectionTitle: {
fontSize: 16,
fontWeight: 'bold',
},
});
4.3.3 Pull-to-Refresh
import React, {useState, useCallback} from 'react';
import {FlatList, RefreshControl} from 'react-native';
function RefreshableList(): React.JSX.Element {
const [refreshing, setRefreshing] = useState(false);
const [data, setData] = useState(initialData);
const onRefresh = useCallback(async () => {
setRefreshing(true);
const newData = await fetchData();
setData(newData);
setRefreshing(false);
}, []);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
/>
);
}
4.3.4 Infinite Scroll / Pagination
function PaginatedList(): React.JSX.Element {
const [data, setData] = useState<Item[]>([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const loadMore = useCallback(async () => {
if (loading) return;
setLoading(true);
const newItems = await fetchPage(page + 1);
setData(prev => [...prev, ...newItems]);
setPage(prev => prev + 1);
setLoading(false);
}, [page, loading]);
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={item => item.id}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loading ? <ActivityIndicator /> : null}
/>
);
}
4.4 Handling User Input
4.4.1 Touchables and Pressable
import React from 'react';
import {
Pressable,
TouchableOpacity,
TouchableHighlight,
Text,
StyleSheet,
} from 'react-native';
function ButtonExamples(): React.JSX.Element {
return (
<>
{/* Pressable — most flexible, recommended */}
<Pressable
style={({pressed}) => [
styles.button,
pressed && styles.buttonPressed,
]}
onPress={() => console.log('Pressed')}
onLongPress={() => console.log('Long pressed')}
hitSlop={10}>
<Text style={styles.buttonText}>Pressable</Text>
</Pressable>
{/* TouchableOpacity — fades on press */}
<TouchableOpacity
style={styles.button}
activeOpacity={0.7}
onPress={() => console.log('Pressed')}>
<Text style={styles.buttonText}>TouchableOpacity</Text>
</TouchableOpacity>
{/* TouchableHighlight — highlights on press */}
<TouchableHighlight
style={styles.button}
underlayColor="#ddd"
onPress={() => console.log('Pressed')}>
<Text style={styles.buttonText}>TouchableHighlight</Text>
</TouchableHighlight>
</>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#3498db',
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
marginBottom: 12,
},
buttonPressed: {
backgroundColor: '#2980b9',
transform: [{scale: 0.98}],
},
buttonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
textAlign: 'center',
},
});
4.4.2 Gesture Handling
import React from 'react';
import {View, PanResponder, Animated} from 'react-native';
function DraggableBox(): React.JSX.Element {
const pan = React.useRef(new Animated.ValueXY()).current;
const panResponder = React.useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderMove: Animated.event(
[null, {dx: pan.x, dy: pan.y}],
{useNativeDriver: false},
),
onPanResponderRelease: () => {
Animated.spring(pan, {
toValue: {x: 0, y: 0},
useNativeDriver: true,
}).start();
},
}),
).current;
return (
<Animated.View
style={[
{
width: 100,
height: 100,
backgroundColor: '#3498db',
borderRadius: 8,
transform: [{translateX: pan.x}, {translateY: pan.y}],
},
]}
{...panResponder.panHandlers}
/>
);
}
4.5 Navigation
React Native does not include a built-in navigation system. The community-standard library is React Navigation (@react-navigation).
4.5.1 Installing React Navigation
npm install @react-navigation/native @react-navigation/native-stack
# Required peer dependencies
npm install react-native-screens react-native-safe-area-context
For iOS, install pods:
cd ios && pod install && cd ..
4.5.2 Stack Navigation
import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {Button, Text, View} from 'react-native';
type RootStackParamList = {
Home: undefined;
Details: {itemId: number};
};
const Stack = createNativeStackNavigator<RootStackParamList>();
function HomeScreen({navigation}: any): React.JSX.Element {
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details', {itemId: 42})}
/>
</View>
);
}
function DetailsScreen({route}: any): React.JSX.Element {
const {itemId} = route.params;
return (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text>Details for item {itemId}</Text>
</View>
);
}
function App(): React.JSX.Element {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
4.5.3 Tab Navigation
npm install @react-navigation/bottom-tabs
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
function App(): React.JSX.Element {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
4.5.4 Drawer Navigation
npm install @react-navigation/drawer react-native-gesture-handler react-native-reanimated
import {createDrawerNavigator} from '@react-navigation/drawer';
const Drawer = createDrawerNavigator();
function App(): React.JSX.Element {
return (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Settings" component={SettingsScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
4.6 Networking
4.6.1 Fetch API
React Native includes the standard fetch API:
async function fetchUsers() {
try {
const response = await fetch('https://api.example.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch failed:', error);
throw error;
}
}
4.6.2 POST Requests
async function createUser(name: string, email: string) {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer YOUR_TOKEN',
},
body: JSON.stringify({name, email}),
});
return response.json();
}
4.6.3 Using Data in Components
import React, {useState, useEffect} from 'react';
import {FlatList, Text, View, ActivityIndicator} from 'react-native';
interface User {
id: number;
name: string;
}
function UserList(): React.JSX.Element {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <ActivityIndicator size="large" />;
if (error) return <Text>Error: {error}</Text>;
return (
<FlatList
data={users}
keyExtractor={item => String(item.id)}
renderItem={({item}) => (
<View style={{padding: 16}}>
<Text>{item.name}</Text>
</View>
)}
/>
);
}
4.6.4 WebSocket
const ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = () => {
ws.send('Hello from React Native!');
};
ws.onmessage = (event) => {
console.log('Received:', event.data);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('WebSocket closed');
};
4.7 State Management
4.7.1 useState
import React, {useState} from 'react';
import {View, Text, Button} from 'react-native';
function Counter(): React.JSX.Element {
const [count, setCount] = useState(0);
return (
<View>
<Text>Count: {count}</Text>
<Button title="Increment" onPress={() => setCount(c => c + 1)} />
<Button title="Decrement" onPress={() => setCount(c => c - 1)} />
<Button title="Reset" onPress={() => setCount(0)} />
</View>
);
}
4.7.2 useReducer
import React, {useReducer} from 'react';
import {View, Text, Button} from 'react-native';
type State = {count: number};
type Action = {type: 'increment'} | {type: 'decrement'} | {type: 'reset'};
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return {count: 0};
}
}
function Counter(): React.JSX.Element {
const [state, dispatch] = useReducer(reducer, {count: 0});
return (
<View>
<Text>Count: {state.count}</Text>
<Button title="+" onPress={() => dispatch({type: 'increment'})} />
<Button title="-" onPress={() => dispatch({type: 'decrement'})} />
<Button title="Reset" onPress={() => dispatch({type: 'reset'})} />
</View>
);
}
4.7.3 Context API
import React, {createContext, useContext, useState} from 'react';
interface ThemeContextType {
isDark: boolean;
toggle: () => void;
}
const ThemeContext = createContext<ThemeContextType>({
isDark: false,
toggle: () => {},
});
function ThemeProvider({children}: {children: React.ReactNode}) {
const [isDark, setIsDark] = useState(false);
const toggle = () => setIsDark(prev => !prev);
return (
<ThemeContext.Provider value={{isDark, toggle}}>
{children}
</ThemeContext.Provider>
);
}
function useTheme() {
return useContext(ThemeContext);
}
// Usage in a component
function ThemedScreen(): React.JSX.Element {
const {isDark, toggle} = useTheme();
return (
<View style={{backgroundColor: isDark ? '#333' : '#fff', flex: 1}}>
<Text style={{color: isDark ? '#fff' : '#333'}}>
Current theme: {isDark ? 'Dark' : 'Light'}
</Text>
<Button title="Toggle Theme" onPress={toggle} />
</View>
);
}
4.8 Animations
4.8.1 Animated API
import React, {useRef, useEffect} from 'react';
import {Animated, View, Text, StyleSheet} from 'react-native';
function FadeInView({children}: {children: React.ReactNode}): React.JSX.Element {
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}).start();
}, [fadeAnim]);
return (
<Animated.View style={{opacity: fadeAnim}}>
{children}
</Animated.View>
);
}
4.8.2 Animation Types
// Spring animation
Animated.spring(value, {
toValue: 1,
friction: 5,
tension: 40,
useNativeDriver: true,
}).start();
// Timing animation with easing
import {Easing} from 'react-native';
Animated.timing(value, {
toValue: 1,
duration: 500,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
useNativeDriver: true,
}).start();
// Decay animation (momentum-based)
Animated.decay(value, {
velocity: 0.95,
deceleration: 0.997,
useNativeDriver: true,
}).start();
4.8.3 Composing Animations
// Sequential
Animated.sequence([
Animated.timing(opacity, {toValue: 1, duration: 300, useNativeDriver: true}),
Animated.timing(translateY, {toValue: 0, duration: 300, useNativeDriver: true}),
]).start();
// Parallel
Animated.parallel([
Animated.timing(opacity, {toValue: 1, duration: 300, useNativeDriver: true}),
Animated.timing(scale, {toValue: 1, duration: 300, useNativeDriver: true}),
]).start();
// Stagger
Animated.stagger(100, [
Animated.timing(item1Opacity, {toValue: 1, duration: 300, useNativeDriver: true}),
Animated.timing(item2Opacity, {toValue: 1, duration: 300, useNativeDriver: true}),
Animated.timing(item3Opacity, {toValue: 1, duration: 300, useNativeDriver: true}),
]).start();
// Loop
Animated.loop(
Animated.sequence([
Animated.timing(rotation, {toValue: 1, duration: 1000, useNativeDriver: true}),
Animated.timing(rotation, {toValue: 0, duration: 1000, useNativeDriver: true}),
]),
).start();
4.8.4 Interpolation
const spinValue = useRef(new Animated.Value(0)).current;
const spin = spinValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
});
const backgroundColor = animValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: ['#e74c3c', '#f39c12', '#2ecc71'],
});
return (
<Animated.View
style={{
transform: [{rotate: spin}],
backgroundColor,
}}
/>
);
4.9 Platform-Specific Code
4.9.1 Platform Module
import {Platform} from 'react-native';
// Simple check
if (Platform.OS === 'ios') {
// iOS-specific code
} else if (Platform.OS === 'android') {
// Android-specific code
}
// Version check
if (Platform.OS === 'android' && Platform.Version >= 33) {
// Android 13+ specific code
}
// Platform.select
const styles = StyleSheet.create({
container: {
...Platform.select({
ios: {paddingTop: 44},
android: {paddingTop: 0},
default: {paddingTop: 0},
}),
},
});
4.9.2 Platform-Specific Files
React Native automatically picks platform-specific files based on extension:
Button.ios.tsx ← Used on iOS
Button.android.tsx ← Used on Android
Button.tsx ← Fallback for other platforms
Import normally — Metro resolves the correct file:
import Button from './Button'; // Resolves to .ios.tsx or .android.tsx
4.10 Safe Area Handling
import {SafeAreaView, StatusBar, StyleSheet} from 'react-native';
function App(): React.JSX.Element {
return (
<>
<StatusBar barStyle="dark-content" backgroundColor="#fff" />
<SafeAreaView style={styles.container}>
{/* Your content here — respects notch, status bar, home indicator */}
</SafeAreaView>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
For more control, use the community react-native-safe-area-context library:
import {SafeAreaProvider, useSafeAreaInsets} from 'react-native-safe-area-context';
function Screen(): React.JSX.Element {
const insets = useSafeAreaInsets();
return (
<View style={{paddingTop: insets.top, paddingBottom: insets.bottom}}>
{/* Content */}
</View>
);
}
function App(): React.JSX.Element {
return (
<SafeAreaProvider>
<Screen />
</SafeAreaProvider>
);
}
4.11 Debugging
4.11.1 React Native DevTools
React Native ships with a built-in debugger based on Chrome DevTools. Access it via the Dev Menu (shake device or Cmd+D / Cmd+M).
The inspector proxy exposes these endpoints on the Metro dev server:
| Endpoint | Purpose |
|---|---|
/json |
List available debug targets |
/json/list |
Same as /json |
/json/version |
Version information |
/inspector/device |
Device connection endpoint |
/inspector/debug |
Debugger connection endpoint |
4.11.2 Console Logging
console.log('Info message');
console.warn('Warning message'); // Yellow box
console.error('Error message'); // Red box
Logs appear in:
- Metro terminal output
- React Native DevTools console
- Xcode console (iOS)
- Android Studio Logcat (Android): filter by
ReactNativeJS
4.11.3 React Developer Tools
Install the standalone React DevTools:
npx react-devtools
This opens a window that connects to your running app, allowing you to inspect the component tree, props, state, and hooks.
4.11.4 Network Debugging
To inspect network requests, add to your app's entry point:
// For debug builds only
if (__DEV__) {
// XMLHttpRequest is used by fetch under the hood
const originalXHR = global.XMLHttpRequest;
// Use a network interceptor library or React Native DevTools
}
4.12 The New Architecture
React Native 0.86 defaults to the New Architecture, which consists of three pillars:
4.12.1 Fabric (New Renderer)
Fabric replaces the old UI manager with a C++ renderer that enables:
- Synchronous layout and measurement
- Concurrent rendering support
- Better integration with platform views
- Improved performance through shared ownership of UI trees
No action required for JavaScript-level code — Fabric is transparent to your components.
4.12.2 TurboModules
TurboModules replace the old bridge-based native modules with:
- Lazy loading (modules loaded only when first accessed)
- Direct JavaScript-to-native calls (no JSON serialization over the bridge)
- Type-safe interfaces via Codegen
4.12.3 Codegen
Codegen generates type-safe C++ interfaces from your JavaScript/TypeScript/Flow type definitions. This ensures that JavaScript and native code always agree on the shape of data being passed.
5. Common Tasks
5.1 Storing Data Locally
5.1.1 AsyncStorage
npm install @react-native-async-storage/async-storage
import AsyncStorage from '@react-native-async-storage/async-storage';
// Store
await AsyncStorage.setItem('user_token', 'abc123');
await AsyncStorage.setItem('user_prefs', JSON.stringify({theme: 'dark'}));
// Retrieve
const token = await AsyncStorage.getItem('user_token');
const prefs = JSON.parse(await AsyncStorage.getItem('user_prefs') ?? '{}');
// Remove
await AsyncStorage.removeItem('user_token');
// Clear all
await AsyncStorage.clear();
// Multi operations
await AsyncStorage.multiSet([
['key1', 'value1'],
['key2', 'value2'],
]);
const values = await AsyncStorage.multiGet(['key1', 'key2']);
// values = [['key1', 'value1'], ['key2', 'value2']]
5.2 Handling Permissions
npm install react-native-permissions
import {check, request, PERMISSIONS, RESULTS} from 'react-native-permissions';
import {Platform} from 'react-native';
async function requestCameraPermission() {
const permission = Platform.select({
ios: PERMISSIONS.IOS.CAMERA,
android: PERMISSIONS.ANDROID.CAMERA,
});
if (!permission) return;
const result = await check(permission);
switch (result) {
case RESULTS.UNAVAILABLE:
console.log('Feature not available on this device');
break;
case RESULTS.DENIED:
const requestResult = await request(permission);
console.log('Request result:', requestResult);
break;
case RESULTS.GRANTED:
console.log('Permission already granted');
break;
case RESULTS.BLOCKED:
console.log('Permission blocked — direct user to Settings');
break;
}
}
5.3 Handling Deep Links
import {Linking} from 'react-native';
// Open a URL
await Linking.openURL('https://example.com');
// Open phone dialer
await Linking.openURL('tel:+15551234567');
// Open email
await Linking.openURL('mailto:[email removed]');
// Open app settings
await Linking.openSettings();
// Listen for incoming deep links
useEffect(() => {
const handleDeepLink = ({url}: {url: string}) => {
console.log('Deep link received:', url);
// Parse and navigate
};
// Check if app was opened via deep link
Linking.getInitialURL().then(url => {
if (url) handleDeepLink({url});
});
// Listen for deep links while app is open
const subscription = Linking.addEventListener('url', handleDeepLink);
return () => subscription.remove();
}, []);
5.4 Clipboard Operations
import Clipboard from '@react-native-clipboard/clipboard';
// Copy to clipboard
Clipboard.setString('Text to copy');
// Read from clipboard
const text = await Clipboard.getString();
5.5 Using the Camera
npm install react-native-image-picker
import {launchCamera, launchImageLibrary} from 'react-native-image-picker';
// Take a photo
const result = await launchCamera({
mediaType: 'photo',
quality: 0.8,
maxWidth: 1024,
maxHeight: 1024,
});
if (result.assets?.[0]) {
const photo = result.assets[0];
console.log('URI:', photo.uri);
console.log('Size:', photo.fileSize);
}
// Pick from gallery
const picked = await launchImageLibrary({
mediaType: 'mixed',
selectionLimit: 5,
});
5.6 Showing Alerts and Action Sheets
import {Alert, ActionSheetIOS, Platform} from 'react-native';
// Simple alert
Alert.alert('Title', 'Message');
// Alert with buttons
Alert.alert(
'Delete Item',
'Are you sure you want to delete this item?',
[
{text: 'Cancel', style: 'cancel'},
{text: 'Delete', style: 'destructive', onPress: () => deleteItem()},
],
);
// Prompt for text input (iOS only)
Alert.prompt(
'Enter Name',
'Please enter your display name:',
(text) => console.log('Entered:', text),
);
// Action Sheet (iOS)
if (Platform.OS === 'ios') {
ActionSheetIOS.showActionSheetWithOptions(
{
options: ['Cancel', 'Share', 'Delete'],
cancelButtonIndex: 0,
destructiveButtonIndex: 2,
},
(buttonIndex) => {
if (buttonIndex === 1) share();
if (buttonIndex === 2) deleteItem();
},
);
}
5.7 Handling the Keyboard
import React from 'react';
import {
KeyboardAvoidingView,
Platform,
TextInput,
StyleSheet,
View,
} from 'react-native';
function ChatInput(): React.JSX.Element {
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={Platform.OS === 'ios' ? 88 : 0}
style={styles.container}>
<View style={styles.inputRow}>
<TextInput style={styles.input} placeholder="Type a message..." />
</View>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
inputRow: {
flexDirection: 'row',
padding: 8,
borderTopWidth: 1,
borderTopColor: '#eee',
},
input: {
flex: 1,
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 20,
paddingHorizontal: 16,
paddingVertical: 8,
},
});
5.7.1 Keyboard Events
import {Keyboard} from 'react-native';
useEffect(() => {
const showSub = Keyboard.addListener('keyboardDidShow', (event) => {
console.log('Keyboard height:', event.endCoordinates.height);
});
const hideSub = Keyboard.addListener('keyboardDidHide', () => {
console.log('Keyboard hidden');
});
return () => {
showSub.remove();
hideSub.remove();
};
}, []);
// Dismiss keyboard programmatically
Keyboard.dismiss();
5.8 StatusBar Configuration
import {StatusBar} from 'react-native';
// Global configuration
<StatusBar
barStyle="dark-content" // 'dark-content' | 'light-content'
backgroundColor="#ffffff" // Android only
translucent={false} // Android only
hidden={false}
/>
// Imperative API
StatusBar.setBarStyle('light-content');
StatusBar.setBackgroundColor('#000000'); // Android only
StatusBar.setHidden(true);
5.9 Sharing Content
import {Share} from 'react-native';
async function shareContent() {
try {
const result = await Share.share({
title: 'Check this out',
message: 'I found something interesting: https://example.com',
url: 'https://example.com', // iOS only
});
if (result.action === Share.sharedAction) {
console.log('Shared successfully');
} else if (result.action === Share.dismissedAction) {
console.log('Share dismissed');
}
} catch (error) {
console.error('Share error:', error);
}
}
5.10 App State Handling
import {AppState} from 'react-native';
useEffect(() => {
const subscription = AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'active') {
console.log('App came to foreground');
// Refresh data, reconnect sockets, etc.
} else if (nextAppState === 'background') {
console.log('App went to background');
// Save state, pause tasks, etc.
} else if (nextAppState === 'inactive') {
console.log('App is transitioning (iOS only)');
}
});
return () => subscription.remove();
}, []);
5.11 Dimensions and Window Size
import {Dimensions, useWindowDimensions} from 'react-native';
// Hook (recommended — updates on rotation)
function ResponsiveLayout(): React.JSX.Element {
const {width, height} = useWindowDimensions();
const isLandscape = width > height;
return (
<View style={{flexDirection: isLandscape ? 'row' : 'column'}}>
{/* Responsive layout */}
</View>
);
}
// Static API (doesn't update on rotation)
const {width, height} = Dimensions.get('window');
const screenDimensions = Dimensions.get('screen'); // Includes status bar
5.12 Accessibility
import {View, Text, TouchableOpacity} from 'react-native';
function AccessibleButton(): React.JSX.Element {
return (
<TouchableOpacity
accessible={true}
accessibilityLabel="Delete this item"
accessibilityHint="Double tap to permanently remove this item"
accessibilityRole="button"
accessibilityState={{disabled: false}}
onPress={handleDelete}>
<Text>Delete</Text>
</TouchableOpacity>
);
}
// Live region (announces changes to screen reader)
<Text accessibilityLiveRegion="polite">
{itemCount} items remaining
</Text>
// Accessibility roles
// 'button', 'link', 'header', 'search', 'image', 'text',
// 'adjustable', 'alert', 'checkbox', 'radio', 'switch', 'tab', etc.
5.13 Timers and Intervals
import React, {useEffect, useRef} from 'react';
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (delay === null) return;
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}, [delay]);
}
// Usage
function Countdown(): React.JSX.Element {
const [seconds, setSeconds] = useState(60);
useInterval(() => {
setSeconds(s => s - 1);
}, seconds > 0 ? 1000 : null);
return <Text>{seconds}s remaining</Text>;
}
5.14 Modal Dialogs
import React, {useState} from 'react';
import {Modal, View, Text, Pressable, StyleSheet} from 'react-native';
function ModalExample(): React.JSX.Element {
const [visible, setVisible] = useState(false);
return (
<>
<Pressable onPress={() => setVisible(true)}>
<Text>Open Modal</Text>
</Pressable>
<Modal
animationType="slide"
transparent={true}
visible={visible}
onRequestClose={() => setVisible(false)}>
<View style={styles.modalOverlay}>
<View style={styles.modalContent}>
<Text style={styles.modalTitle}>Modal Title</Text>
<Text>This is a modal dialog.</Text>
<Pressable
style={styles.closeButton}
onPress={() => setVisible(false)}>
<Text style={styles.closeText}>Close</Text>
</Pressable>
</View>
</View>
</Modal>
</>
);
}
const styles = StyleSheet.create({
modalOverlay: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0,0,0,0.5)',
},
modalContent: {
backgroundColor: 'white',
borderRadius: 16,
padding: 24,
width: '80%',
alignItems: 'center',
},
modalTitle: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 12,
},
closeButton: {
marginTop: 16,
paddingVertical: 8,
paddingHorizontal: 24,
backgroundColor: '#3498db',
borderRadius: 8,
},
closeText: {
color: 'white',
fontWeight: '600',
},
});
5.15 Appearance and Dark Mode
import {useColorScheme, Appearance} from 'react-native';
function ThemedApp(): React.JSX.Element {
const colorScheme = useColorScheme(); // 'light' | 'dark' | null
const theme = {
background: colorScheme === 'dark' ? '#1a1a1a' : '#ffffff',
text: colorScheme === 'dark' ? '#ffffff' : '#1a1a1a',
card: colorScheme === 'dark' ? '#2a2a2a' : '#f5f5f5',
};
return (
<View style={{flex: 1, backgroundColor: theme.background}}>
<Text style={{color: theme.text}}>
Current scheme: {colorScheme}
</Text>
</View>
);
}
// Imperative API
const current = Appearance.getColorScheme();
const listener = Appearance.addChangeListener(({colorScheme}) => {
console.log('Theme changed to:', colorScheme);
});
5.16 Building Production Apps
5.16.1 Bundle for Production
# Create the JavaScript bundle
npx react-native bundle \
--platform ios \
--dev false \
--entry-file index.js \
--bundle-output ios/main.jsbundle \
--assets-dest ios
npx react-native bundle \
--platform android \
--dev false \
--entry-file index.js \
--bundle-output android/app/src/main/assets/index.android.bundle \
--assets-dest android/app/src/main/res
5.16.2 Build Android Release APK/AAB
cd android
./gradlew assembleRelease # APK
./gradlew bundleRelease # AAB (recommended for Play Store)
Output locations:
android/app/build/outputs/apk/release/app-release.apk
android/app/build/outputs/bundle/release/app-release.aab
Important
Release builds require a signing key. Configure signing in
android/app/build.gradleundersigningConfigsbefore building. Without proper signing, the app will not install on devices or be accepted by the Play Store.
5.16.3 Build iOS Release
- Open
ios/MyProject.xcworkspacein Xcode - Select "Any iOS Device" as the build target
- Product > Archive
- In the Organizer window, select the archive and click "Distribute App"
5.17 Environment Variables
# Set during Metro start
RCT_METRO_PORT=8088 npx react-native start
# Check dev mode in code
if (__DEV__) {
console.log('Running in development mode');
}
For more comprehensive environment variable management, use a library like react-native-config:
npm install react-native-config
# .env
API_URL=https://api.example.com
API_KEY=your_api_key
import Config from 'react-native-config';
console.log(Config.API_URL); // 'https://api.example.com'
5.18 Custom Fonts
5.18.1 Using Static Assets
- Place font files in
assets/fonts/directory - Add to
react-native.config.js:
module.exports = {
project: {
ios: {},
android: {},
},
assets: ['./assets/fonts/'],
};
- Link the fonts:
npx react-native-asset
- Use in styles:
const styles = StyleSheet.create({
text: {
fontFamily: 'Montserrat-Bold', // Exact filename without extension
},
});
5.19 Error Boundaries
import React from 'react';
import {View, Text, Button, StyleSheet} from 'react-native';
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends React.Component<
{children: React.ReactNode},
ErrorBoundaryState
> {
constructor(props: {children: React.ReactNode}) {
super(props);
this.state = {hasError: false, error: null};
}
static getDerivedStateFromError(error: Error) {
return {hasError: true, error};
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// Log to crash reporting service
console.error('ErrorBoundary caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<View style={styles.container}>
<Text style={styles.title}>Something went wrong</Text>
<Text style={styles.message}>{this.state.error?.message}</Text>
<Button
title="Try Again"
onPress={() => this.setState({hasError: false, error: null})}
/>
</View>
);
}
return this.props.children;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
title: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 8,
},
message: {
color: '#666',
marginBottom: 16,
textAlign: 'center',
},
});
5.20 Performance Optimization
5.20.1 Memoization
import React, {useMemo, useCallback} from 'react';
// Memoize expensive computations
const sortedData = useMemo(() => {
return data.sort((a, b) => a.name.localeCompare(b.name));
}, [data]);
// Memoize callbacks
const handlePress = useCallback((id: string) => {
setSelectedId(id);
}, []);
// Memoize components
const MemoizedItem = React.memo(function Item({title, onPress}: ItemProps) {
return (
<Pressable onPress={onPress}>
<Text>{title}</Text>
</Pressable>
);
});
5.20.2 FlatList Optimization
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
// Performance props
removeClippedSubviews={true} // Unmount off-screen views
maxToRenderPerBatch={10} // Items per render batch
updateCellsBatchingPeriod={50} // Batch update interval (ms)
windowSize={5} // Render window multiplier
initialNumToRender={10} // Initial render count
getItemLayout={(data, index) => ({ // Skip measurement
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
/>
5.20.3 Image Optimization
// Use FastImage for better caching (community library)
import FastImage from 'react-native-fast-image';
<FastImage
style={{width: 200, height: 200}}
source={{
uri: 'https://example.com/photo.jpg',
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable,
}}
resizeMode={FastImage.resizeMode.cover}
/>
5.21 Testing
5.21.1 Unit Tests with Jest
// __tests__/utils.test.ts
import {formatCurrency, validateEmail} from '../src/utils';
describe('formatCurrency', () => {
it('formats dollars correctly', () => {
expect(formatCurrency(1234.5)).toBe('$1,234.50');
});
it('handles zero', () => {
expect(formatCurrency(0)).toBe('$0.00');
});
});
describe('validateEmail', () => {
it('accepts valid emails', () => {
expect(validateEmail('[email removed]')).toBe(true);
});
it('rejects invalid emails', () => {
expect(validateEmail('not-an-email')).toBe(false);
});
});
Run tests:
npx jest
# With coverage
npx jest --coverage
# Watch mode
npx jest --watch
5.21.2 Component Tests
// __tests__/Counter.test.tsx
import React from 'react';
import {render, fireEvent} from '@testing-library/react-native';
import Counter from '../src/Counter';
describe('Counter', () => {
it('renders initial count', () => {
const {getByText} = render(<Counter />);
expect(getByText('Count: 0')).toBeTruthy();
});
it('increments on press', () => {
const {getByText} = render(<Counter />);
fireEvent.press(getByText('Increment'));
expect(getByText('Count: 1')).toBeTruthy();
});
});
5.21.3 Snapshot Tests
import React from 'react';
import renderer from 'react-test-renderer';
import App from '../App';
it('renders correctly', () => {
const tree = renderer.create(<App />).toJSON();
expect(tree).toMatchSnapshot();
});
5.22 Upgrading React Native
# Check available upgrade
npx react-native upgrade
# Or use the upgrade helper website to see diffs between versions:
# https://react-native-community.github.io/upgrade-helper/
Caution
Major version upgrades can involve breaking changes to native code, Gradle configurations, Podfile settings, and third-party library compatibility. Always read the release notes, back up your project, and test thoroughly on both platforms after upgrading.
5.23 Using Native Modules (TurboModules)
With the New Architecture, native modules use Codegen for type-safe bridging.
5.23.1 Defining a TurboModule Spec (TypeScript)
// NativeCalculator.ts
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
add(a: number, b: number): number;
multiply(a: number, b: number): Promise<number>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('Calculator');
5.23.2 Using the Module
import NativeCalculator from './NativeCalculator';
const sum = NativeCalculator.add(3, 4); // Synchronous: 7
const product = await NativeCalculator.multiply(3, 4); // Async: 12
5.24 Handling Back Button (Android)
import {BackHandler} from 'react-native';
useEffect(() => {
const backAction = () => {
if (canGoBack) {
goBack();
return true; // Prevent default behavior
}
return false; // Let system handle it
};
const backHandler = BackHandler.addEventListener(
'hardwareBackPress',
backAction,
);
return () => backHandler.remove();
}, [canGoBack]);
5.25 Vibration and Haptic Feedback
import {Vibration} from 'react-native';
// Simple vibration
Vibration.vibrate();
// Duration (Android only; iOS ignores the duration)
Vibration.vibrate(500);
// Pattern: [wait, vibrate, wait, vibrate, ...]
Vibration.vibrate([500, 1000, 500, 2000]);
// Cancel
Vibration.cancel();
5.26 Using the PixelRatio API
import {PixelRatio} from 'react-native';
// Get device pixel density
const density = PixelRatio.get(); // e.g., 2 for @2x, 3 for @3x
// Convert layout size to pixel size
const pixelSize = PixelRatio.getPixelSizeForLayoutSize(100);
// Round to nearest pixel
const rounded = PixelRatio.roundToNearestPixel(8.4);
// Get font scale
const fontScale = PixelRatio.getFontScale();
6. Troubleshooting
6.1 Build and Setup Issues
Error: "Unable to load script from assets 'index.android.bundle'"
Cause: The JavaScript bundle is not being served to the Android emulator or device.
Solution:
- Ensure Metro is running:
npx react-native start - If using a physical device, ensure it's on the same network as your development machine
- For the emulator, reverse the port:
adb reverse tcp:8081 tcp:8081 - If the port was changed, use the correct port:
adb reverse tcp:8088 tcp:8088
Prevention: Always start Metro before running the app. If using a custom port via RCT_METRO_PORT, ensure it matches everywhere.
Error: "Build failed" with CocoaPods issues
Cause: Stale or corrupt CocoaPods cache, or mismatched dependency versions.
Solution:
- Clean and reinstall:
cd ios rm -rf Pods Podfile.lock pod cache clean --all pod install cd .. - If that fails, also clean the build folder:
cd ios xcodebuild clean cd ..
Prevention: Run pod install after every npm install that adds native dependencies.
Error: "Inconsistent Podfile.lock checksums for hermes-engine"
Cause: The Podfile.lock has cached checksums that don't match the current Hermes binary. This is a known issue (GitHub #54891) that can occur after upgrading React Native or switching branches.
Solution:
- Delete the lockfile and reinstall:
cd ios rm Podfile.lock pod install cd .. - If the error persists, clean the CocoaPods cache for Hermes specifically:
pod cache clean hermes-engine pod install
Prevention: Always delete Podfile.lock before running pod install after a React Native version upgrade.
Error: "SDK location not found"
Cause: ANDROID_HOME environment variable is not set or points to the wrong location.
Solution:
- Find your Android SDK location (check Android Studio > Settings > SDK Manager)
- Set the environment variable:
export ANDROID_HOME=$HOME/Library/Android/sdk # macOS export ANDROID_HOME=$HOME/Android/Sdk # Linux - Add to your shell profile (
~/.zshrc,~/.bashrc)
Prevention: Add ANDROID_HOME to your shell profile during initial setup.
Error: "foojay-resolver-convention 0.5.0 incompatible with Gradle 9.0.0"
Cause: The foojay-resolver-convention plugin version bundled with React Native is too old for Gradle 9.x. This is a known issue (GitHub #56287).
Solution:
- In
android/settings.gradle, update the plugin version:plugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '0.9.0' } - Or downgrade to a compatible Gradle version in
android/gradle/wrapper/gradle-wrapper.properties:distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
Prevention: Check Gradle compatibility before upgrading Android build tools.
6.2 Runtime Errors
Error: "Invariant Violation: requireNativeComponent: 'RCTView' was not found"
Cause: Native components are not properly linked, typically after adding a library with native code.
Solution:
- For iOS:
cd ios && pod install && cd .. npx react-native run-ios - For Android, rebuild:
cd android && ./gradlew clean && cd .. npx react-native run-android
Prevention: Always run pod install (iOS) and rebuild after adding libraries with native dependencies.
Error: "VirtualizedLists should never be nested inside plain ScrollViews"
Cause: A FlatList or SectionList is placed inside a ScrollView, creating competing scroll containers.
Solution:
- Replace the outer
ScrollViewwith aView - Or use
FlatList'sListHeaderComponentandListFooterComponentfor content above/below the list - Or use the
nestedScrollEnabledprop (Android only, not recommended)
// Instead of this:
<ScrollView>
<Header />
<FlatList data={data} renderItem={renderItem} />
</ScrollView>
// Do this:
<FlatList
data={data}
renderItem={renderItem}
ListHeaderComponent={<Header />}
/>
Prevention: Design layouts so FlatList/SectionList is the outermost scrolling container.
Error: "Animations do not run correctly in React Navigation"
Cause: A known issue (GitHub #56347) where Animated animations conflict with React Navigation's transition animations on iOS, particularly when using Animated.timing inside screen components.
Solution:
- Use
useNativeDriver: truein all animations - Defer animations until after the navigation transition completes:
useFocusEffect( useCallback(() => { // Start animations here, after screen is focused Animated.timing(opacity, { toValue: 1, duration: 300, useNativeDriver: true, }).start(); }, []), ); - Consider using
react-native-reanimatedfor more complex animation scenarios
Prevention: Always use useNativeDriver: true and tie animations to navigation lifecycle events.
Error: "Network request failed"
Cause: This intermittent error (GitHub #56480) can stem from multiple causes: network timeout, SSL certificate issues, or Android cleartext traffic restrictions.
Solution:
- On Android, ensure cleartext traffic is allowed for development:
<!-- android/app/src/main/AndroidManifest.xml --> <application android:usesCleartextTraffic="true" ...> - Check network connectivity
- Add error retry logic:
async function fetchWithRetry(url: string, retries = 3) { for (let i = 0; i < retries; i++) { try { const response = await fetch(url); return response; } catch (error) { if (i === retries - 1) throw error; await new Promise(r => setTimeout(r, 1000 * (i + 1))); } } }
Prevention: Implement retry logic and timeout handling for all network requests.
Error: App crashes on startup with "TurboModule performVoidMethodInvocation - SIGABRT" (iOS)
Cause: A known issue on iOS 26 (GitHub #54859) where TurboModule initialization triggers a crash during app startup. This affects apps using the New Architecture.
Solution:
- Check if you're running on the latest React Native patch version — this may have a fix
- If on an older version, try disabling specific TurboModules that may be causing the crash
- Check the GitHub issue for the latest workarounds specific to your iOS version
Prevention: Test on the latest iOS betas early and track known issues in the React Native repository.
Error: "EXC_BAD_ACCESS in production after upgrading"
Cause: Memory corruption, often related to the Fabric renderer or native module lifecycle changes between React Native versions (GitHub #53774).
Solution:
- Clean all build caches:
cd ios rm -rf Pods Podfile.lock build DerivedData pod install cd .. npx react-native run-ios --mode Release - If persists, check for native modules that haven't been updated for the current React Native version
- Test with native modules removed one at a time to isolate the cause
Prevention: After upgrading React Native, test release builds on physical devices before deploying to users.
6.3 UI and Layout Issues
Visual jump when using RefreshControl
Cause: A longstanding issue (GitHub #31461) where RefreshControl causes the list to jump to an unexpected scroll position when the refresh animation begins.
Solution:
- Set
progressViewOffseton Android to adjust the refresh indicator position:<FlatList refreshControl={ <RefreshControl refreshing={refreshing} onRefresh={onRefresh} progressViewOffset={Platform.OS === 'android' ? 50 : 0} /> } /> - On iOS, wrapping in a fixed-height container can help mitigate the jump
Prevention: Test pull-to-refresh behavior across both platforms during development.
Nested Text not focusable by hardware keyboard
Cause: A known issue (GitHub #35194) where nested <Text> components inside tappable elements cannot receive focus from hardware keyboards or accessibility devices.
Solution:
- Use
accessible={true}on the outermost tappable container - Set
accessibilityRole="link"oraccessibilityRole="button"on the container - For keyboard navigation, consider wrapping tappable text in a
Pressable:<Pressable onPress={handlePress} accessible accessibilityRole="link"> <Text> Read the <Text style={{color: 'blue'}}>documentation</Text> </Text> </Pressable>
Prevention: Always test with hardware keyboards and screen readers.
Nested View and Text vertical alignment issue
Cause: When mixing View and Text components in a row layout, vertical alignment can be inconsistent because Text components use text baseline alignment while View components use their box model (GitHub #31955).
Solution:
- Use
alignItems: 'center'on the parent flex row - Ensure the
ViewandTexthave explicit matching heights - Apply
textAlignVertical: 'center'(Android) toTextcomponents
Prevention: Use consistent component types in flex rows. Wrap mixed content in View containers with explicit alignment.
letterSpacing not working properly
Cause: A known issue (GitHub #54823) where letterSpacing behaves inconsistently across platforms, particularly with certain font families and sizes.
Solution:
- Test
letterSpacingvalues on both platforms - Use platform-specific values if needed:
const styles = StyleSheet.create({ text: { letterSpacing: Platform.select({ios: 1.5, android: 2}), }, });
Prevention: Always verify text styling on both iOS and Android.
KeyboardAvoidingView not keeping input visible (Android 15)
Cause: On Android 15, the keyboard insets reported to KeyboardAvoidingView can be incorrect, causing the input field to remain hidden behind the keyboard (GitHub #49759).
Solution:
- Use
behavior="height"instead ofbehavior="padding"on Android:<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={Platform.select({ios: 88, android: 0})}> {/* Input fields */} </KeyboardAvoidingView> - Alternatively, use
android:windowSoftInputMode="adjustResize"inAndroidManifest.xml - Consider
react-native-keyboard-aware-scroll-viewas a community alternative
Prevention: Test keyboard behavior on the latest Android versions during development.
getStatusBarHeightPx initialized before DecorView attached
Cause: On some Android devices, StatusBar height methods are called before the view hierarchy is ready (GitHub #53251), returning 0 or incorrect values.
Solution:
- Defer status bar height reads to after
onLayout:<View onLayout={() => { // StatusBar height is now reliable const height = StatusBar.currentHeight; }} /> - Use
react-native-safe-area-contextwhich handles this timing correctly
Prevention: Avoid reading StatusBar.currentHeight in component constructors or early lifecycle methods.
6.4 Development Server Issues
Error: "Could not connect to development server"
Cause: Metro is not running, or the device cannot reach the dev server.
Solution:
- Start Metro:
npx react-native start - On Android emulator, set up port forwarding:
adb reverse tcp:8081 tcp:8081 - On physical device, ensure same Wi-Fi network and correct IP
- Check if another process is using port 8081:
lsof -i :8081 - Use a different port:
RCT_METRO_PORT=8088 npx react-native start
Prevention: Kill stale Metro processes before starting a new one.
Error: "EMFILE: too many open files"
Cause: Metro's file watcher exceeds the OS file descriptor limit.
Solution:
- Install Watchman (recommended for all platforms):
brew install watchman # macOS - On Linux, increase the limit:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf sudo sysctl -p
Prevention: Install Watchman as part of initial setup.
Metro bundler stuck or stale cache
Cause: Metro's cache can become corrupted after branch switches, dependency changes, or interrupted builds.
Solution:
- Start Metro with a clean cache:
npx react-native start --reset-cache - Delete the Metro cache directory manually:
rm -rf $TMPDIR/metro-* rm -rf $TMPDIR/haste-map-*
Prevention: Use --reset-cache after major changes like upgrading React Native or switching branches with different dependency trees.
6.5 Android-Specific Issues
Broken code completion / IntelliSense in Android Studio
Cause: A known issue (GitHub #55517) where Android Studio's code completion fails for React Native projects due to Gradle sync or indexing issues with the React Native Gradle plugin.
Solution:
- Invalidate caches and restart: File > Invalidate Caches / Restart
- Re-sync Gradle: File > Sync Project with Gradle Files
- Ensure Android Studio is updated to the latest stable version
Prevention: Keep Android Studio and the Android Gradle plugin at compatible versions.
Hermes profiling: heap snapshot fails with OkHttp WebSocket overflow
Cause: When taking heap snapshots via Hermes CDP, OkHttp's WebSocket queue can overflow (GitHub #56470), causing the connection to close before the snapshot completes.
Solution:
- Use React Native DevTools instead of Chrome DevTools for heap profiling
- Reduce memory pressure before taking snapshots (close other apps, reduce data in state)
- If using a custom CDP client, increase WebSocket buffer sizes
Prevention: Use the built-in React Native DevTools for Hermes debugging tasks.
HermesSamplingProfiler JNI method mismatch
Cause: A known issue (GitHub #56126) where the JNI "disable" method is incorrectly mapped to the native enable function, potentially causing profiling to behave unexpectedly on Android.
Solution:
- Check if a patch is available for your React Native version
- Avoid using the Hermes sampling profiler until the fix is released
- Use the JavaScript profiler in React Native DevTools as an alternative
Prevention: Monitor the GitHub issue for resolution in upcoming releases.
6.6 Performance Issues
App feels slow / janky scrolling
Cause: Heavy computation on the JavaScript thread blocks the UI.
Solution:
- Move to native driver for animations:
Animated.timing(value, { toValue: 1, useNativeDriver: true, // Key for performance }).start(); - Optimize
FlatList(see section 5.20.2) - Use
React.memofor list items - Avoid inline functions in
renderItem:// Bad — creates new function every render renderItem={({item}) => <Item data={item} onPress={() => select(item.id)} />} // Good — stable reference const renderItem = useCallback(({item}) => ( <Item data={item} onPress={handlePress} /> ), [handlePress]);
Prevention: Profile regularly with the React DevTools Profiler and Systrace.
Large bundle size
Cause: Unused code, large assets, or unoptimized dependencies.
Solution:
- Enable inline requires (already default in Metro config:
inlineRequires: true) - Analyze bundle size:
npx react-native bundle \ --platform android \ --dev false \ --entry-file index.js \ --bundle-output /tmp/bundle.js \ --sourcemap-output /tmp/bundle.map npx source-map-explorer /tmp/bundle.js /tmp/bundle.map - Remove unused dependencies from
package.json - Use dynamic imports for rarely-used features
Prevention: Monitor bundle size as part of your CI/CD pipeline.
6.7 General Debugging Strategies
Full cache reset (nuclear option)
When nothing else works, clear everything:
# Stop Metro
# Kill all running emulators/simulators
# Clear JS/Node caches
rm -rf node_modules
npm cache clean --force
npm install
# Clear Metro cache
rm -rf $TMPDIR/metro-*
rm -rf $TMPDIR/haste-map-*
# Clear iOS caches
cd ios
rm -rf Pods Podfile.lock build
pod cache clean --all
pod install
cd ..
# Clear Android caches
cd android
./gradlew clean
cd ..
# Clear Xcode DerivedData
rm -rf ~/Library/Developer/Xcode/DerivedData
# Restart Metro with clean cache
npx react-native start --reset-cache
Warning
The full cache reset deletes significant build artifacts and caches. Expect longer build times for the next build as everything is regenerated from scratch. Only use this when targeted solutions have failed.
7. FAQ
Q: What JavaScript engine does React Native use?
React Native uses Hermes as its default JavaScript engine. Hermes is an open-source engine optimized for React Native with ahead-of-time compilation, lower memory usage, and faster startup compared to JavaScriptCore. It is built in and enabled by default.
Q: Can I use TypeScript with React Native?
Yes. New projects created with npx @react-native-community/cli@latest init include TypeScript support out of the box, with a tsconfig.json that extends @react-native/typescript-config. You can write all your components in .tsx files.
Q: What is the New Architecture?
The New Architecture is the modernized internals of React Native, consisting of three components: Fabric (the new renderer for UI), TurboModules (the new native module system with lazy loading and direct calls), and Codegen (type-safe bridge code generation). It is enabled by default in React Native 0.86.
Q: Can I build for both iOS and Android from a single codebase?
Yes, that is React Native's primary purpose. You write your UI components and business logic once in JavaScript/TypeScript, and React Native renders native views on each platform. Platform-specific code can be handled via Platform.OS checks, Platform.select(), or platform-specific file extensions (.ios.tsx, .android.tsx).
Q: Can I use npm packages from the web ecosystem?
Many npm packages work in React Native, particularly those that are pure JavaScript. Packages that depend on browser-specific APIs (DOM, window, document, Canvas, etc.) will not work. Always check a package's React Native compatibility before installing.
Q: How do I handle different screen sizes?
Use Flexbox for responsive layouts, useWindowDimensions() for dimension-aware rendering, percentage-based widths/heights, and PixelRatio for pixel-perfect designs. React Native's Flexbox implementation handles density-independent pixels automatically.
Q: What is Metro?
Metro is React Native's JavaScript bundler. It watches your source files, transforms them through Babel, resolves dependencies, and serves the bundle to your app. It runs on port 8081 by default (configurable via RCT_METRO_PORT). The default configuration resolves modules using the field order ['react-native', 'browser', 'main'] and supports the platforms ['android', 'ios'].
Q: How do I add custom native code?
For the New Architecture, create a TurboModule specification in TypeScript/Flow, then implement the native side in Swift/Objective-C (iOS) or Kotlin/Java (Android). Codegen generates the C++ bridge code. For Fabric components, define a component spec and implement the native view. See the Admin Guide for architecture details.
Q: What is Fast Refresh?
Fast Refresh is React Native's hot reloading system. When you save a file, Metro sends only the changed module to the running app, which updates the component tree while preserving state. It handles both functional and class components, and falls back to a full reload when structural changes require it.
Q: How do I debug React Native apps?
React Native includes a built-in debugger based on Chrome DevTools (@react-native/debugger-frontend). Access it via the Dev Menu (shake device, Cmd+D, or Cmd+M). You can also use npx react-devtools for component tree inspection, or the inspector proxy endpoints (/json, /json/list, /json/version) for custom debugger integrations.
Q: Do I need a Mac to build for iOS?
Yes. iOS apps can only be built on macOS with Xcode installed. There is no way around this Apple platform requirement. For teams without Mac hardware, cloud Mac services (like Mac Stadium or GitHub Actions macOS runners) are common solutions.
Q: How do I handle navigation between screens?
React Native does not include a built-in navigation library. The community standard is React Navigation (@react-navigation), which provides stack, tab, drawer, and other navigation patterns. See section 4.5 for setup and usage examples.
Q: Can I use React Native with Expo?
Yes. Expo is a popular framework built on top of React Native that provides additional tooling, pre-configured native modules, and a managed workflow. This guide focuses on the bare React Native CLI workflow, but many concepts apply to both.
Q: How do I handle images with different resolutions?
For local images, provide @1x, @2x, and @3x variants:
assets/
logo.png (100x100)
logo@2x.png (200x200)
logo@3x.png (300x300)
React Native's asset system (backed by @react-native/assets-registry) automatically selects the correct variant for the device's pixel density.
Q: What is the difference between ScrollView and FlatList?
ScrollView renders all children at once — suitable for small, fixed-size content. FlatList virtualizes its children, only rendering items visible on screen — required for large or dynamic lists. Use FlatList (or SectionList) whenever your list has more than a few dozen items.
Q: How do I handle forms and validation?
React Native provides TextInput for form fields. For form management and validation, community libraries like Formik or React Hook Form work well with React Native. These are standard React libraries that are fully compatible.
Q: How do I add splash screens?
React Native does not include built-in splash screen support. Use the community library react-native-splash-screen or react-native-bootsplash. Configure the native splash screen in Xcode (LaunchScreen.storyboard) and Android (styles.xml theme).
Q: Can I use web CSS in React Native?
No. React Native uses a JavaScript-based styling system that resembles CSS but has key differences: all properties are camelCase, dimensions are unitless (density-independent pixels), flexDirection defaults to column, there is no cascading or inheritance (except within Text), and not all CSS properties are supported. Use StyleSheet.create() for all styles.
Q: How do I handle offline/no-network scenarios?
Use the NetInfo community library (@react-native-community/netinfo) to detect connectivity status. Combine with local storage (AsyncStorage or SQLite) to cache data for offline use. Implement retry queues for failed network requests.
Q: What are the supported platforms?
React Native officially supports iOS and Android. The Metro configuration defaults to platforms: ['android', 'ios']. Community-driven efforts extend support to Windows, macOS, web, and other targets via out-of-tree platforms.
Q: How do I handle app permissions?
Use the react-native-permissions community library for a cross-platform permissions API. On iOS, configure required permission descriptions in Info.plist. On Android, declare permissions in AndroidManifest.xml. See section 5.2 for examples.
Q: Why is my app's startup time slow?
Common causes: large JavaScript bundle, too many modules loaded at startup, heavy initial rendering. Solutions: enable Hermes (default), use inline requires (enabled by default in Metro), lazy-load screens, minimize initial render tree, and profile with Hermes bytecode compilation stats.
Q: How do I use environment variables?
React Native provides __DEV__ globally to check if you're in development mode. For custom environment variables, use the react-native-config library or set them via RCT_METRO_PORT and similar env vars at build time. Do not hardcode secrets in your JavaScript bundle.
Q: Can I share code between React Native and React web?
Yes, with caveats. Business logic, hooks, state management, and API calls can be shared. UI components cannot be shared directly because React Native uses View/Text instead of div/span. Libraries like React Native Web bridge this gap by mapping React Native components to web equivalents.
Q: How do I profile my app's performance?
Use the React DevTools Profiler for component render profiling. Use Systrace for native-level profiling. On Android, use Android Studio's profiler. On iOS, use Xcode Instruments. The Hermes profiler provides JavaScript-specific profiling data.
Q: What is the __DEV__ variable?
__DEV__ is a global boolean set to true in development builds and false in production builds. Use it to conditionally include debug logging, development tools, or mock data:
if (__DEV__) {
console.log('Debug info:', data);
}
Q: How do I handle the Android hardware back button?
Use BackHandler from React Native (see section 5.24). Return true from the handler to prevent the default back behavior, or false to allow it. React Navigation handles back button integration automatically for its navigation stacks.