React Native
Printed from:
Complete React Native Cheatsheet
Targets React Native 0.76+ with the New Architecture (Fabric + TurboModules) on by default. Most teams now scaffold via Expo rather than the bare RN CLI.
Table of Contents
- Setup
- Project Structure
- Core Components
- Styling
- Layout (Flexbox)
- Navigation
- Forms & Input
- Lists
- Images & Assets
- Networking & Data
- Storage
- Platform APIs
- Animations
- Gestures
- Native Modules
- Permissions
- Debugging & DevTools
- Building & Publishing
- Troubleshooting
- Quick Reference
Setup
12345678910111213141516171819# Recommended: Expo (managed workflow + EAS Build)
npx create-expo-app@latest my-app
cd my-app
pnpm install
pnpm start # Metro + DevTools
# press a / i / w for Android / iOS / web
# Bare RN CLI (no Expo)
npx @react-native-community/cli@latest init MyApp
cd MyApp
pnpm start # Metro
pnpm ios # requires Xcode
pnpm android # requires Android Studio + JDK 17
# Requirements
# - Node 20+, watchman (macOS), pnpm/npm/yarn
# - iOS: Xcode 15+, CocoaPods, iOS simulator
# - Android: Android Studio, SDK 34+, JDK 17, an emulator or device
123456# Run on a real device (Expo Go)
pnpm start # scan QR with Expo Go app
# Or build a dev client
pnpm exec expo install expo-dev-client
pnpm exec eas build --profile development --platform ios
Project Structure
1234567891011121314my-app/ ├── app/ # Expo Router file-based routing │ ├── _layout.tsx │ ├── index.tsx # / │ └── (tabs)/ # tab group ├── assets/ # images, fonts ├── components/ ├── App.tsx # entry (non-Expo Router) or RootLayout ├── app.json / app.config.ts # Expo config ├── babel.config.js ├── metro.config.js ├── package.json └── tsconfig.json
Core Components
123456789101112import {
View, Text, Image, ScrollView, FlatList, SectionList,
Pressable, TouchableOpacity, TextInput, Switch, ActivityIndicator,
Modal, RefreshControl, StatusBar, SafeAreaView, KeyboardAvoidingView,
} from "react-native";
export default function App() {
return (
<SafeAreaView style={{ flex: 1 }}>
<StatusBar barStyle="dark-content" />
<View style={{ padding: 16 }}>
<Text style={{ fontSize: 24, fontWeight: "600" }}>Hello</Text>
<Pressable onPress={() => console.log("tap")} style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}>
<Text>Press me</Text>
</Pressable>
</View>
</SafeAreaView>
);
}
Use
Pressablefor new code (more flexible thanTouchableOpacity). For safe areas in Expo, preferreact-native-safe-area-context(SafeAreaViewfrom there) over the core component.
Styling
12345678910111213141516171819202122232425import { StyleSheet, Platform, useColorScheme } from "react-native";
const styles = StyleSheet.create({
card: {
padding: 16,
borderRadius: 12,
backgroundColor: "white",
// iOS shadow
shadowColor: "#000", shadowOpacity: 0.1, shadowRadius: 8, shadowOffset: { width: 0, height: 2 },
// Android shadow
elevation: 3,
},
title: { fontSize: 18, fontWeight: "600", color: Platform.select({ ios: "#111", android: "#222" }) },
});
// Dark mode
const scheme = useColorScheme(); // "light" | "dark" | null
const bg = scheme === "dark" ? "#000" : "#fff";
// Style arrays + conditional
<View style={[styles.card, active && { borderWidth: 1 }]} />
// Inline (fine for one-offs)
<View style={{ marginTop: 8 }} />
Popular styling libraries: NativeWind (Tailwind for RN), Tamagui, Restyle, styled-components/native, Unistyles.
Layout (Flexbox)
1234567891011121314151617// All Views default to flexDirection: "column" and flex: 0
<View style={{
flex: 1,
flexDirection: "row", // "column" | "row" | "row-reverse" | "column-reverse"
justifyContent: "space-between", // main axis
alignItems: "center", // cross axis
gap: 12, // RN 0.71+
padding: 16,
}} />
// Absolute positioning
<View style={{ position: "absolute", top: 0, right: 0 }} />
// Dimensions
import { Dimensions, useWindowDimensions } from "react-native";
const { width, height } = useWindowDimensions(); // reactive
Navigation
Expo Router (file-system, recommended)
1234567891011121314151617181920// app/_layout.tsx
import { Stack } from "expo-router";
export default function RootLayout() { return <Stack />; }
// app/index.tsx
import { Link, useRouter } from "expo-router";
export default function Home() {
const router = useRouter();
return (
<>
<Link href="/profile/123">Profile</Link>
<Pressable onPress={() => router.push("/settings")}><Text>Settings</Text></Pressable>
</>
);
}
// app/profile/[id].tsx
import { useLocalSearchParams } from "expo-router";
export default function Profile() {
const { id } = useLocalSearchParams<{ id: string }>();
return <Text>{id}</Text>;
}
React Navigation (classic)
123pnpm add @react-navigation/native @react-navigation/native-stack pnpm add react-native-screens react-native-safe-area-context
123456789101112import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
const Stack = createNativeStackNavigator();
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={Home} />
<Stack.Screen name="Profile" component={Profile} />
</Stack.Navigator>
</NavigationContainer>
// Inside a screen:
function Home({ navigation }) {
return <Button title="Go" onPress={() => navigation.navigate("Profile", { id: 1 })} />;
}
Forms & Input
123456const [text, setText] = useState("");
<TextInput
value={text}
onChangeText={setText}
placeholder="Email"
keyboardType="email-address"
autoCapitalize="none"
autoCorrect={false}
returnKeyType="next"
secureTextEntry={false}
style={{ borderWidth: 1, padding: 8, borderRadius: 8 }}
/>
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"} style={{ flex: 1 }}>
{/* form */}
</KeyboardAvoidingView>
Form libraries: react-hook-form, Formik. Validation: zod, yup, valibot.
Lists
1234import { FlatList, SectionList } from "react-native";
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Row item={item} />}
ItemSeparatorComponent={() => <View style={{ height: 1, backgroundColor: "#eee" }} />}
ListEmptyComponent={<Text>No items</Text>}
ListHeaderComponent={<Header />}
onEndReached={loadMore}
onEndReachedThreshold={0.5}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
estimatedItemSize={80} // perf hint
/>
For huge / virtualised lists, prefer @shopify/flash-list or LegendList (significantly faster than FlatList on heavy lists).
Images & Assets
12345678910import { Image } from "react-native";
// Or for Expo (faster, supports caching/blurhash):
import { Image } from "expo-image";
<Image source={require("./assets/logo.png")} style={{ width: 100, height: 100 }} />
<Image source={{ uri: "https://example.com/img.jpg" }} style={{ width: 200, height: 200 }} contentFit="cover" />
// Image dimensions
Image.resolveAssetSource(require("./assets/logo.png"));
Fonts (Expo):
1234import { useFonts } from "expo-font";
const [loaded] = useFonts({ Inter: require("./assets/Inter.ttf") });
if (!loaded) return null;
Networking & Data
1234567891011// fetch works out of the box
const res = await fetch("https://api.example.com/users");
const json = await res.json();
// Recommended data layer: TanStack Query
import { useQuery } from "@tanstack/react-query";
const { data, isLoading, error } = useQuery({
queryKey: ["user", id],
queryFn: () => fetch(`/api/user/${id}`).then(r => r.json()),
});
WebSockets: new WebSocket("wss://...").
GraphQL: Apollo / urql / TanStack Query + graphql-request.
Important: On iOS, only HTTPS URLs work by default (ATS). Whitelist exceptions in
Info.plistif you need plain HTTP.
Storage
1234pnpm add @react-native-async-storage/async-storage
# or for Expo:
pnpm exec expo install @react-native-async-storage/async-storage
123456import AsyncStorage from "@react-native-async-storage/async-storage";
await AsyncStorage.setItem("@token", "abc");
const token = await AsyncStorage.getItem("@token");
await AsyncStorage.removeItem("@token");
Other options: MMKV (fastest, sync), expo-secure-store (Keychain/Keystore), expo-sqlite, WatermelonDB, Drizzle/SQLite, PowerSync.
Platform APIs
12345678910111213141516import { Platform, Linking, Share, Alert, Vibration, Clipboard } from "react-native";
Platform.OS; // "ios" | "android" | "web"
Platform.Version;
Platform.select({ ios: "A", android: "B" });
Linking.openURL("tel:+15551234");
Linking.openURL("mailto:hi@example.com");
await Share.share({ message: "Hello", url: "https://example.com" });
Alert.alert("Title", "Message", [
{ text: "Cancel", style: "cancel" },
{ text: "OK", onPress: () => {} },
]);
Expo modules expose hardware features cleanly:
12expo install expo-location expo-camera expo-notifications expo-haptics expo-clipboard expo-battery
Animations
1234567891011121314151617// Built-in Animated API (legacy but everywhere)
import { Animated } from "react-native";
const opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(opacity, { toValue: 1, duration: 300, useNativeDriver: true }).start();
}, []);
<Animated.View style={{ opacity }} />
// react-native-reanimated v3 (the modern choice)
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated";
const x = useSharedValue(0);
const style = useAnimatedStyle(() => ({ transform: [{ translateX: x.value }] }));
<Animated.View style={[styles.box, style]} />
<Button title="Slide" onPress={() => (x.value = withSpring(150))} />
For shared element transitions and complex layouts, Reanimated + react-native-gesture-handler is the standard stack.
Gestures
12pnpm add react-native-gesture-handler
1234567import { GestureHandlerRootView, GestureDetector, Gesture } from "react-native-gesture-handler";
const tap = Gesture.Tap().onEnd(() => console.log("tapped"));
const pan = Gesture.Pan().onChange((e) => { x.value += e.changeX; });
<GestureHandlerRootView style={{ flex: 1 }}>
<GestureDetector gesture={Gesture.Race(tap, pan)}>
<Animated.View style={[box, style]} />
</GestureDetector>
</GestureHandlerRootView>
Native Modules
Most teams don't write native modules anymore — Expo modules or community packages cover everything. When you must:
1234# Expo modules API (recommended)
pnpm exec expo install expo-modules-core
pnpm exec expo-modules-cli init my-module
This generates Swift (iOS) + Kotlin (Android) module skeletons exposed via TypeScript.
12345// JS side
import { requireNativeModule } from "expo-modules-core";
const MyModule = requireNativeModule("MyModule");
MyModule.doSomething("hello");
Bare RN uses TurboModules + codegen via the New Architecture; see react-native-codegen docs.
Permissions
Expo:
12345import * as Location from "expo-location";
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== "granted") return;
const loc = await Location.getCurrentPositionAsync();
Bare RN: react-native-permissions.
iOS: declare every permission and a usage string in Info.plist / app.json infoPlist keys (NSCameraUsageDescription, NSLocationWhenInUseUsageDescription, …).
Android: add entries to AndroidManifest.xml and runtime requests for dangerous permissions.
Debugging & DevTools
1234567891011121314151617181920# Toggle the dev menu
# iOS sim: Cmd-D (or shake on device)
# Android: Cmd-M / Ctrl-M (or shake)
# Reload
# Cmd-R / Ctrl-R, or "r" in Metro terminal
# React DevTools
npx react-devtools
# Inspect Hermes engine in Chrome
# Dev menu → "Open Debugger" (uses new RN DevTools panel in 0.76+)
# Performance / profiling
# Dev menu → "Show Perf Monitor"
# Flipper is now deprecated — use the new RN DevTools.
# Network inspection
console.log + React Native DevTools, or Reactotron, or @logan/reactotron
Common log helpers:
123console.log / warn / error
LogBox.ignoreLogs(["Setting a timer"]);
Building & Publishing
EAS (Expo Application Services) — preferred
123456789npm i -g eas-cli
eas login
eas build:configure
eas build --platform ios # cloud build, signs automatically
eas build --platform android
eas submit -p ios # submit to App Store / TestFlight
eas submit -p android
eas update --branch production # OTA update (JS-only changes)
Bare RN
123456789# iOS
cd ios && pod install && cd ..
pnpm exec react-native run-ios --configuration Release
# Or open ios/MyApp.xcworkspace in Xcode → Archive → Distribute
# Android
cd android && ./gradlew assembleRelease # APK
./gradlew bundleRelease # AAB for Play Store
CI: GitHub Actions + EAS Build, or Bitrise, or Fastlane for fully native pipelines.
Troubleshooting
12345678910111213141516171819202122232425262728# "Unable to resolve module" / Metro stuck
pnpm exec react-native start --reset-cache
rm -rf node_modules .expo .metro-health-check* && pnpm install
# Pods out of sync (iOS)
cd ios && pod deintegrate && pod install --repo-update && cd ..
# Gradle weirdness (Android)
cd android && ./gradlew --stop && ./gradlew clean && cd ..
# "Native module cannot be null"
# - rebuild the dev client / native app after adding a native dep
# - clear Xcode DerivedData
# Hermes vs JSC
# Hermes is default; check `expo.jsEngine` / RN config if you flipped it
# Slow Android emulator
# Use a real device or a recent x86_64 system image with HAXM/Hyper-V
# White screen on launch
# - check Metro is running (pnpm start)
# - check device is on the same Wi-Fi
# - shake → "Settings" → set Debug server host
# Asset not found in release builds
# Run `pnpm exec expo prebuild` or rebuild after adding to assets/
Quick Reference
12345678910111213141516171819202122232425262728293031// Components
<View><Text>x</Text></View>
<Pressable onPress={...}><Text>tap</Text></Pressable>
<TextInput value={v} onChangeText={setV} />
<FlatList data={d} keyExtractor={i=>i.id} renderItem={({item})=><Row/>} />
<Image source={{uri}} style={{width,height}} />
// StyleSheet
const s = StyleSheet.create({ box: { padding: 8 } });
// Hooks
const scheme = useColorScheme();
const { width } = useWindowDimensions();
// Platform
Platform.OS Platform.select({ios, android})
// Navigation (Expo Router)
<Link href="/x" />
const router = useRouter(); router.push("/x");
const { id } = useLocalSearchParams<{id:string}>();
// Animation (Reanimated)
const v = useSharedValue(0);
const s = useAnimatedStyle(() => ({ transform: [{ translateX: v.value }] }));
v.value = withSpring(100);
// EAS
eas build -p ios eas submit -p ios
eas update --branch prod
Tip: start every new project with Expo + Expo Router + EAS. The bare RN CLI is now mostly a fallback for apps that need deep native customisation; everything else is faster, more reliable, and more cross-platform on Expo.
Continue Learning
Discover more cheatsheets to boost your productivity