React Native Basics
10 examples to get you started with React Native Fundamentals - 7 basic and 3 intermediate.
Search across all documentation pages
10 examples to get you started with React Native Fundamentals - 7 basic and 3 intermediate.
React Native apps need a native runtime. The fastest path is Expo, which ships a preconfigured Metro bundler, dev client, and common native modules.
npx create-expo-app@latest MyApp
cd MyApp
npx expo startScan the QR code with Expo Go on a physical device, or press i / a in the terminal to open the iOS Simulator or Android emulator. Edit App.tsx - every example below can replace that file to run immediately.
Tooling: These examples target Expo SDK 57, React Native 0.86, and React 19.2.3. TypeScript (
.tsx) is used throughout; Expo projects include it by default.
The two primitives every React Native screen is built from - View for layout containers and Text for anything the user reads.
import { View, Text, StyleSheet } from "react-native";
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello, React Native!</Text>
<Text style={styles.subtitle}>Built with View and Text.</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center" },
title: { fontSize: 24, fontWeight: "bold" },
subtitle: { fontSize: 16, color: "#666", marginTop: 8 },
});<div> or <p> - View maps to a native container and Text maps to a native text node<Text> - putting a raw string inside View throws a runtime errorflex: 1 on the container makes it fill the entire screen, which is the standard root layout patternfontSize, not font-size)Related: Views, Text & Core Components - nesting rules,
TextInput, and platform rendering quirks
Use StyleSheet.create to define reusable, validated style objects instead of inline literals.
import { View, Text, StyleSheet } from "react-native";
export default function App() {
return (
<View style={styles.card}>
<Text style={styles.heading}>StyleSheet.create</Text>
<Text style={styles.body}>
Styles are defined once and referenced by key.
</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
margin: 16,
padding: 20,
backgroundColor: "#f0f4ff",
borderRadius: 12,
borderWidth: 1,
borderColor: "#c7d2fe",
},
heading: { fontSize: 18, fontWeight: "600", marginBottom: 8 },
body: { fontSize: 14, lineHeight: 20, color: "#374151" },
});StyleSheet.create validates property names at dev time and sends a style ID to the native layer instead of a full object each renderstyle={{ margin: 16 }}) work for one-offs, but StyleSheet is preferred for anything reused or composedstyle={[styles.base, isActive && styles.active]} - later entries override earlier onesem/rem unitsRelated: Views, Text & Core Components - how styles attach to native views
React Native uses Flexbox for all layout. The default flexDirection is column, not row like many web layouts assume.
import { View, Text, StyleSheet } from "react-native";
export default function App() {
return (
<View style={styles.screen}>
<View style={styles.header}>
<Text style={styles.headerText}>Header</Text>
</View>
<View style={styles.row}>
<View style={[styles.box, styles.boxA]}>
<Text>A</Text>
</View>
<View style={[styles.box, styles.boxB]}>
<Text>B</Text>
</View>
</View>
<View style={styles.footer}>
<Text style={styles.footerText}>Footer</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 16 },
header: { padding: 12, backgroundColor: "#dbeafe", borderRadius: 8 },
headerText: { fontWeight: "600" },
row: { flex: 1, flexDirection: "row", gap: 12, marginVertical: 16 },
box: { flex: 1, justifyContent: "center", alignItems: "center", borderRadius: 8 },
boxA: { backgroundColor: "#bbf7d0" },
boxB: { backgroundColor: "#fde68a" },
footer: { padding: 12, backgroundColor: "#f3f4f6", borderRadius: 8 },
footerText: { textAlign: "center", color: "#6b7280" },
});flex: 1 on sibling views makes them share remaining space equally along the parent's main axisflexDirection: "row" switches the main axis to horizontal - essential for side-by-side layoutsjustifyContent aligns children along the main axis; alignItems aligns along the cross axisgap property (RN 0.71+) adds spacing between flex children without margin hacksRelated: Dimensions & Responsive Layout - breakpoints, tablets, and adaptive grids
Pressable is the modern touch primitive - it exposes press state so you can style feedback without a separate opacity wrapper.
import { useState } from "react";
import { View, Text, Pressable, StyleSheet } from "react-native";
export default function App() {
const [lastPress, setLastPress] = useState("No taps yet");
return (
<View style={styles.container}>
<Pressable
onPress={() => setLastPress(new Date().toLocaleTimeString())}
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed,
]}
>
<Text style={styles.buttonText}>Tap me</Text>
</Pressable>
<Text style={styles.status}>{lastPress}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center", gap: 16 },
button: {
backgroundColor: "#2563eb",
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
},
buttonPressed: { backgroundColor: "#1d4ed8", transform: [{ scale: 0.97 }] },
buttonText: { color: "#fff", fontWeight: "600", fontSize: 16 },
status: { color: "#6b7280" },
});style prop accepts a function ({ pressed }) => [...] so pressed-state styling stays declarativeonPress fires on a completed tap; use onPressIn / onPressOut for press-and-hold or drag scenarioshitSlop to enlarge the touch target without changing visual size - critical for accessibility on small iconsPressable supersedes TouchableOpacity and TouchableHighlight in new codeRelated: Event Handling & Touchables -
hitSlop, long press, and gesture layering
useState works the same as in React for web - a state change schedules a re-render of the component tree.
import { useState } from "react";
import { View, Text, Pressable, StyleSheet } from "react-native";
export default function App() {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<Text style={styles.count}>{count}</Text>
<View style={styles.row}>
<Pressable style={styles.button} onPress={() => setCount((c) => c - 1)}>
<Text style={styles.buttonText}>−</Text>
</Pressable>
<Pressable style={styles.button} onPress={() => setCount((c) => c + 1)}>
<Text style={styles.buttonText}>+</Text>
</Pressable>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center", gap: 24 },
count: { fontSize: 48, fontWeight: "bold" },
row: { flexDirection: "row", gap: 12 },
button: {
backgroundColor: "#2563eb",
width: 56,
height: 56,
borderRadius: 28,
justifyContent: "center",
alignItems: "center",
},
buttonText: { color: "#fff", fontSize: 24, fontWeight: "600" },
});count immediately after setCount still returns the old valuesetCount((c) => c + 1) when the new value depends on the previous oneuseState call triggers a re-render of this component and its children - keep state as local as possibleRelated: Props, State & Re-renders on Mobile - how updates cross the JS/native boundary
Display local bundled assets with require() or remote images with a { uri } source object.
import { View, Image, Text, StyleSheet } from "react-native";
const REMOTE_URI =
"https://reactnative.dev/img/tiny_logo.png";
export default function App() {
return (
<View style={styles.container}>
<Image source={require("./assets/icon.png")} style={styles.local} />
<Image source={{ uri: REMOTE_URI }} style={styles.remote} />
<Text style={styles.caption}>Local asset (top) and remote URI (bottom)</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center", gap: 16 },
local: { width: 64, height: 64, borderRadius: 12 },
remote: { width: 64, height: 64 },
caption: { fontSize: 13, color: "#6b7280", textAlign: "center", paddingHorizontal: 24 },
});require("./assets/icon.png") resolves at build time - the bundler includes the file and picks the correct density (@2x, @3x)width and height (or aspectRatio) - RN cannot infer dimensions from a URL aloneresizeMode (cover, contain, stretch) to control how the image fills its boundsexpo-image - covered in the dedicated images guideRelated: Images & Assets - density buckets,
expo-asset, and caching strategies
Wrap screen content in SafeAreaView so it clears notches, status bars, and home indicators.
import { Text, StyleSheet } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
export default function App() {
return (
<SafeAreaView style={styles.safe}>
<Text style={styles.title}>Safe Area Layout</Text>
<Text style={styles.body}>
Content stays below the status bar and above the home indicator.
</Text>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safe: { flex: 1, padding: 20, backgroundColor: "#fff" },
title: { fontSize: 22, fontWeight: "bold", marginBottom: 8 },
body: { fontSize: 15, lineHeight: 22, color: "#4b5563" },
});react-native-safe-area-context, not react-native - the built-in SafeAreaView is iOS-only and deprecated for new appsreact-native-safe-area-context by default; wrap your root in SafeAreaProvider (Expo's template does this)useSafeAreaInsets() when you need per-edge control - e.g., a full-bleed header with only top paddingStatusBar from expo-status-bar to match the status bar style to your background colorRelated: Views, Text & Core Components - screen-level layout primitives
Use Platform.OS for simple branching and Platform.select for platform-specific style values.
import { View, Text, Platform, StyleSheet } from "react-native";
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.label}>
Running on {Platform.OS === "ios" ? "iOS" : "Android"}
</Text>
<View style={styles.card}>
<Text style={styles.cardText}>
{Platform.OS === "ios"
? "San Francisco system font"
: "Roboto system font"}
</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center", gap: 16 },
label: { fontSize: 16, color: "#6b7280" },
card: {
padding: 20,
borderRadius: 12,
backgroundColor: "#f9fafb",
...Platform.select({
ios: {
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: { elevation: 4 },
}),
},
cardText: { fontSize: 15 },
});Platform.OS returns "ios", "android", "web", or "windows" - use it for small conditional branchesPlatform.select({ ios: {...}, android: {...} }) returns the matching value and spreads cleanly into StyleSheet objectsshadow* properties; Android uses elevation - Platform.select is the idiomatic way to handle this split.ios.tsx / .android.tsx file extensions instead of inline conditionalsRelated: Platform-Specific Code - file extensions, native modules, and
Platform.Version
React to screen size changes - rotation, foldables, and tablets - with the useWindowDimensions hook.
import { View, Text, useWindowDimensions, StyleSheet } from "react-native";
export default function App() {
const { width } = useWindowDimensions();
const isTablet = width >= 768;
const columns = isTablet ? 3 : 2;
const gap = isTablet ? 16 : 8;
const horizontalPadding = 32;
const tileSize =
(width - horizontalPadding - gap * (columns - 1)) / columns;
return (
<View style={styles.container}>
<Text style={styles.heading}>
{columns}-column layout ({Math.round(width)}px wide)
</Text>
<View style={[styles.grid, { gap }]}>
{Array.from({ length: 6 }, (_, i) => (
<View key={i} style={[styles.tile, { width: tileSize, height: tileSize }]}>
<Text>Item {i + 1}</Text>
</View>
))}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16 },
heading: { fontSize: 16, fontWeight: "600", marginBottom: 16 },
grid: { flexDirection: "row", flexWrap: "wrap" },
tile: {
backgroundColor: "#e0e7ff",
borderRadius: 8,
justifyContent: "center",
alignItems: "center",
},
});useWindowDimensions re-renders on rotation and window resize, unlike the static Dimensions.get("window") snapshot768 for tablet) as named constants shared across the appwidth minus padding and gaps - numeric dimensions avoid percentage layout surprisesflexBasis and flexWrap rather than absolute positioningRelated: Dimensions & Responsive Layout -
DimensionsAPI, orientation hooks, and foldable support
Combine View, Text, Image, Pressable, StyleSheet, and Platform.select into a reusable, interactive card.
import { useState } from "react";
import {
View,
Text,
Image,
Pressable,
Platform,
StyleSheet,
} from "react-native";
const AVATAR_URI = "https://reactnative.dev/img/tiny_logo.png";
export default function App() {
const [following, setFollowing] = useState(false);
return (
<View style={styles.screen}>
<View style={styles.card}>
<Image source={{ uri: AVATAR_URI }} style={styles.avatar} />
<Text style={styles.name}>Alex Rivera</Text>
<Text style={styles.bio}>React Native developer · 42 projects</Text>
<Pressable
onPress={() => setFollowing((f) => !f)}
style={({ pressed }) => [
styles.followButton,
following && styles.following,
pressed && styles.followPressed,
]}
>
<Text
style={[
styles.followText,
following && styles.followingText,
]}
>
{following ? "Following" : "Follow"}
</Text>
</Pressable>
</View>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, justifyContent: "center", padding: 24, backgroundColor: "#f3f4f6" },
card: {
backgroundColor: "#fff",
borderRadius: 16,
padding: 24,
alignItems: "center",
...Platform.select({
ios: {
shadowColor: "#000",
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.12,
shadowRadius: 8,
},
android: { elevation: 6 },
}),
},
avatar: { width: 80, height: 80, borderRadius: 40, marginBottom: 12 },
name: { fontSize: 20, fontWeight: "bold" },
bio: { fontSize: 14, color: "#6b7280", marginTop: 4, marginBottom: 16 },
followButton: {
backgroundColor: "#2563eb",
paddingHorizontal: 32,
paddingVertical: 10,
borderRadius: 20,
},
following: { backgroundColor: "#e5e7eb" },
followPressed: { opacity: 0.85 },
followText: { color: "#fff", fontWeight: "600" },
followingText: { color: "#374151" },
});View), content (Text, Image), interaction (Pressable), and styling (StyleSheet)useState and derive button label and style from the same boolean - a single source of truthname, bio, avatarUri) as the next step toward a component libraryRelated: Props, State & Re-renders on Mobile - extracting reusable components | Best Practices - patterns for production-ready mobile UI
Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 16, 2026