Getting started
Installation
# Expo (recommended)
npx create-expo-app@latest MyApp
cd MyApp && npx expo start
# Bare React Native CLI
npx react-native@latest init MyApp
cd MyApp
npx react-native run-ios
npx react-native run-android
Quick Example
import { View, Text, StyleSheet } from "react-native";
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello World</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center" },
title: { fontSize: 24, fontWeight: "bold" },
});
Key Differences from Web
- No
<div>,<span>— use<View>,<Text> - All text MUST be inside
<Text>component - No CSS files — styles are JS objects
- No CSS cascade or selectors
- Default
flexDirection: 'column'(web:row) - Units are density-independent pixels
Components
View
import { View, Text } from "react-native";
<View style={{ flex: 1, padding: 20 }}>
<Text>Hello World</Text>
</View>;
The fundamental building block. Like <div> in HTML.
Text
import { Text } from "react-native";
<Text style={{ fontSize: 16, fontWeight: "bold" }}>Hello World</Text>;
All text must be inside <Text>. Nested text inherits styles:
<Text style={{ fontWeight: "bold" }}>
Bold <Text style={{ color: "red" }}>and red</Text>
</Text>
Image
import { Image } from 'react-native';
// Remote image (must specify dimensions)
<Image
source={{ uri: 'https://example.com/img.jpg' }}
style={{ width: 200, height: 200 }}
/>
// Local image
<Image source={require('./assets/logo.png')} />
TextInput
import { TextInput } from "react-native";
<TextInput
value={text}
onChangeText={setText}
placeholder="Enter text"
keyboardType="email-address"
secureTextEntry={false}
multiline={false}
autoCapitalize="none"
/>;
| Prop | Values |
|---|---|
keyboardType |
default numeric email-address phone-pad |
autoCapitalize |
none sentences words characters |
Pressable
import { Pressable, Text } from "react-native";
<Pressable
onPress={() => console.log("Pressed!")}
onLongPress={() => console.log("Long pressed!")}
style={({ pressed }) => [{ opacity: pressed ? 0.5 : 1, padding: 10 }]}
>
<Text>Press me</Text>
</Pressable>;
Modern replacement for TouchableOpacity.
FlatList
import { FlatList } from "react-native";
<FlatList
data={[{ id: "1", title: "Item 1" }]}
renderItem={({ item }) => <Text>{item.title}</Text>}
keyExtractor={(item) => item.id}
onEndReached={loadMore}
refreshing={isRefreshing}
onRefresh={handleRefresh}
numColumns={2}
horizontal={false}
/>;
Use for long lists. Renders only visible items.
FlatList Performance
const renderItem = useCallback(({ item }) => <ItemComponent item={item} />, []);
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.id}
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
/>;
Always memoize renderItem and use getItemLayout if items have fixed height.
SectionList
import { SectionList } from "react-native";
<SectionList
sections={[
{ title: "A", data: ["Alice", "Aaron"] },
{ title: "B", data: ["Bob", "Bill"] },
]}
renderItem={({ item }) => <Text>{item}</Text>}
renderSectionHeader={({ section }) => (
<Text style={{ fontWeight: "bold" }}>{section.title}</Text>
)}
keyExtractor={(item, index) => item + index}
/>;
ScrollView
import { ScrollView } from "react-native";
<ScrollView
horizontal={false}
showsVerticalScrollIndicator={false}
contentContainerStyle={{ padding: 20 }}
>
<Text>Scrollable content</Text>
</ScrollView>;
⚠️ Use FlatList for long lists. ScrollView renders all children at once.
Modal
import { Modal, View, Text, Button } from "react-native";
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => setModalVisible(false)}
>
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Modal content</Text>
<Button title="Close" onPress={() => setModalVisible(false)} />
</View>
</Modal>;
animationType |
Effect |
|---|---|
none |
No animation |
slide |
Slide from bottom |
fade |
Fade in/out |
Other Components
| Component | Purpose |
|---|---|
SafeAreaView |
Renders within safe area (notch/status bar) |
KeyboardAvoidingView |
Adjusts for keyboard (behavior: padding on iOS, height on Android) |
ActivityIndicator |
Loading spinner (size="large" color="#0000ff") |
StatusBar |
Configure status bar (barStyle="dark-content") |
Switch |
Toggle switch (value={isOn} onValueChange={setIsOn}) |
Styling
StyleSheet.create
import { StyleSheet, View, Text } from "react-native";
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
padding: 20,
},
title: {
fontSize: 24,
fontWeight: "bold",
color: "#333",
},
});
<View style={styles.container}>
<Text style={styles.title}>Hello</Text>
</View>;
StyleSheet.create validates styles and provides performance optimizations.
Combining Styles
// Array (last wins on conflict)
<View style={[styles.base, styles.override]} />
// Conditional
<View style={[styles.base, isActive && styles.active]} />
// Inline merge
<View style={[styles.base, { marginTop: 10 }]} />
Flexbox Layout
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "column", // ⚠️ Default 'column' (web is 'row')
justifyContent: "center",
alignItems: "center",
gap: 10,
},
});
| Property | Values |
|---|---|
justifyContent |
flex-start flex-end center space-between space-around space-evenly |
alignItems |
flex-start flex-end center stretch baseline |
CSS vs React Native
| CSS | React Native |
|---|---|
display: flex |
Default (always flexbox) |
flexDirection: row |
Default is column ⚠️ |
flexShrink: 1 |
Default is 0 ⚠️ |
flex: 1 1 0 |
Only single number: flex: 1 |
background-color |
backgroundColor (camelCase) |
border-radius |
borderRadius (no shorthand) |
| CSS classes/cascade | No cascade, no selectors |
| px, rem, em | Unitless numbers (density-independent pixels) |
Common Style Properties
{
// Layout
width: 100,
height: 100,
margin: 10,
marginTop: 5,
padding: 20,
paddingHorizontal: 10,
position: 'absolute',
top: 0,
left: 0,
// Borders
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
// Shadows (iOS)
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
// Shadows (Android)
elevation: 5,
// Text
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
color: '#333',
}
APIs
Platform
import { Platform } from "react-native";
Platform.OS; // 'ios' | 'android' | 'web'
Platform.Version; // Android API level (number) | iOS version (string)
// Platform-specific values
Platform.select({
ios: { marginTop: 20 },
android: { marginTop: 25 },
default: { marginTop: 0 },
});
Platform-specific Files
Button.ios.js // iOS version
Button.android.js // Android version
import Button from './Button'; // Auto-selects
React Native automatically imports the right file based on platform.
Dimensions
import { useWindowDimensions } from "react-native";
function MyComponent() {
const { width, height, scale, fontScale } = useWindowDimensions();
return <View style={{ width: width * 0.9 }} />;
}
Reactive to screen rotation. Prefer this over static Dimensions.get().
Animated API
import { Animated, Easing } from "react-native";
// Create animated value
const fadeAnim = useRef(new Animated.Value(0)).current;
// Timing animation
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
easing: Easing.ease,
useNativeDriver: true,
}).start();
// Spring animation
Animated.spring(fadeAnim, {
toValue: 1,
friction: 7,
tension: 40,
useNativeDriver: true,
}).start();
⚠️ useNativeDriver: true only works with transform and opacity. Cannot animate width, height, backgroundColor.
Composing Animations
// Sequence (one after another)
Animated.sequence([anim1, anim2]).start();
// Parallel (at same time)
Animated.parallel([anim1, anim2]).start();
// Stagger (parallel with delays)
Animated.stagger(100, [anim1, anim2, anim3]).start();
// Loop
Animated.loop(anim).start();
Interpolation
const rotation = fadeAnim.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "360deg"],
});
<Animated.View style={{ transform: [{ rotate: rotation }] }} />;
Animatable components: Animated.View, Animated.Text, Animated.Image, Animated.ScrollView, Animated.FlatList
Linking
import { Linking } from "react-native";
Linking.openURL("https://example.com");
Linking.openURL("tel:+1234567890");
Linking.openURL("mailto:hello@example.com");
// Deep link handling
const url = await Linking.getInitialURL();
// Listen for URLs
const sub = Linking.addEventListener("url", ({ url }) => {
console.log("Received:", url);
});
return () => sub.remove();
Alert
import { Alert } from "react-native";
Alert.alert("Title", "Message");
Alert.alert("Delete?", "Cannot be undone", [
{ text: "Cancel", style: "cancel" },
{ text: "Delete", onPress: handleDelete, style: "destructive" },
]);
AppState
import { AppState } from "react-native";
useEffect(() => {
const sub = AppState.addEventListener("change", (state) => {
// state: 'active' | 'background' | 'inactive' (iOS only)
if (state === "active") console.log("Foreground");
});
return () => sub.remove();
}, []);
Keyboard
import { Keyboard } from "react-native";
Keyboard.dismiss();
useEffect(() => {
const show = Keyboard.addListener("keyboardDidShow", (e) => {
console.log("Height:", e.endCoordinates.height);
});
const hide = Keyboard.addListener("keyboardDidHide", () => {});
return () => {
show.remove();
hide.remove();
};
}, []);
Hooks
| Hook | Returns | Purpose |
|---|---|---|
useWindowDimensions() |
{ width, height, scale, fontScale } |
Reactive window size |
useColorScheme() |
'light' 'dark' null |
Device color scheme |
Navigation
Setup
npm install @react-navigation/native @react-navigation/native-stack
npx expo install react-native-screens react-native-safe-area-context
import { createStaticNavigation } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
const RootStack = createNativeStackNavigator({
screens: {
Home: HomeScreen,
Profile: {
screen: ProfileScreen,
options: { title: "Profile" },
},
},
});
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return <Navigation />;
}
Navigation Hooks
import { useNavigation, useRoute } from "@react-navigation/native";
function HomeScreen() {
const navigation = useNavigation();
return (
<Button
title="Go to Profile"
onPress={() => navigation.navigate("Profile", { name: "Jane" })}
/>
);
}
function ProfileScreen() {
const route = useRoute();
return <Text>{route.params.name}</Text>;
}
Navigator Types
| Type | Package | Use Case |
|---|---|---|
| Stack | @react-navigation/native-stack |
Card-style push/pop |
| Bottom Tabs | @react-navigation/bottom-tabs |
Tab bar |
| Drawer | @react-navigation/drawer |
Side menu |
| Material Top Tabs | @react-navigation/material-top-tabs |
Swipeable tabs |
Gotchas
Default flexDirection is 'column'
In React Native, flexDirection defaults to 'column'. On web, it defaults to 'row'.
// React Native (default)
<View style={{ flexDirection: 'column' }}> {/* vertical */}
// Web (default)
<div style={{ display: 'flex' }}> {/* horizontal */}
Default flexShrink is 0
React Native uses flexShrink: 0 by default. Web uses flexShrink: 1.
// React Native: items won't shrink below their natural size
<View style={{ flex: 1 }}> {/* flex: 1 0 0% */}
// Web: items will shrink
<div style={{ flex: 1 }}> {/* flex: 1 1 0% */}
All text must be in
You cannot render text outside of a <Text> component.
// ❌ Error
<View>Hello</View>
// ✅ Correct
<View><Text>Hello</Text></View>
No CSS cascade
Styles don't cascade. Each component needs explicit styles.
// ❌ Won't work (no cascade)
<View style={{ color: 'red' }}>
<Text>Not red</Text>
</View>
// ✅ Correct
<View>
<Text style={{ color: 'red' }}>Red text</Text>
</View>
Exception: nested <Text> inherits styles from parent <Text>.
Units are density-independent pixels
All numbers are unitless and represent density-independent pixels.
// ❌ Don't use px, rem, em
<View style={{ width: '100px' }} />
// ✅ Use numbers
<View style={{ width: 100 }} />
useNativeDriver limitations
useNativeDriver: true only works with transform and opacity.
// ✅ Works with useNativeDriver
Animated.timing(anim, {
toValue: 1,
useNativeDriver: true,
}).start();
<Animated.View style={{ opacity: anim }} />
// ❌ Doesn't work with useNativeDriver
<Animated.View style={{ backgroundColor: anim }} />
For non-transform/opacity properties, set useNativeDriver: false.
Also see
- React Native docs (reactnative.dev)
- React Navigation (reactnavigation.org)
- Expo (expo.dev)
- React Native Directory (reactnative.directory)