Quick reference for React Native and Expo — CLI commands, core components, Flexbox styling, hooks, Expo Router navigation, FlatList performance, native device APIs, and common patterns.
ExpoComponentsNavigationHooks
CLI & Expo Commands
Project & Dev Server
npx create-expo-app@latest my-app # scaffold (Expo Router + TS)
npx expo start # start dev server
npx expo start --clear # clear Metro cache
# in terminal: i = iOS, a = Android, r = reload
npx expo install <pkg> # install a compatible native lib
npx expo prebuild # generate ios/ + android/ (CNG)
npx expo run:ios # build + run local iOS
npx expo run:android # build + run local Android
npx expo-doctor # diagnose project issues
EAS (Build / Submit / Update)
eas login
eas build:configure
eas build --platform ios --profile production
eas build -p android --profile preview
eas submit -p ios # upload to App Store Connect
eas update --branch production -m "hotfix" # OTA JS update
Core Components
Primitives
| Component | Use |
| <View> | Layout box (Flexbox container) |
| <Text> | Text — all strings must be inside this |
| <Image> | Image; prefer expo-image for caching |
| <ScrollView> | Small scrollable content, all rendered |
| <FlatList> | Virtualized list for arrays |
| <Pressable> | Touch target (onPress, onLongPress) |
| <TextInput> | Editable text field |
| <SafeAreaView> | Avoid notch / status bar overlap |
Minimal Screen
import { View, Text, Pressable } from "react-native";
export default function Screen() {
return (
<View style={{ flex: 1, padding: 16 }}>
<Text>Hello</Text>
<Pressable onPress={() => {}}>
<Text>Tap</Text>
</Pressable>
</View>
);
}
Styling & Flexbox
StyleSheet
import { StyleSheet } from "react-native";
const s = StyleSheet.create({
box: {
flex: 1,
flexDirection: "row", // default is "column"
justifyContent: "center", // main axis
alignItems: "center", // cross axis
gap: 8,
padding: 16,
backgroundColor: "#0f172a",
borderRadius: 12,
},
});
// combine / conditional: style={[s.box, active && s.active]}
Flexbox Keys
| Property | Values |
| flexDirection | column (default), row |
| justifyContent | center, space-between, flex-end |
| alignItems | center, stretch, flex-start |
| flex | 1 = fill available space |
| gap | spacing between children |
Hooks
State & Effects
const [n, setN] = useState<number>(0);
setN((c) => c + 1); // functional update
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id); // cleanup
}, []); // [] = run once on mount
const onPress = useCallback(() => setN((c) => c + 1), []);
const total = useMemo(() => heavy(items), [items]);
const ref = useRef<TextInput>(null); // ref.current.focus()
Expo Router Navigation
File → Route
app/index.tsx -> /
app/about.tsx -> /about
app/posts/[id].tsx -> /posts/:id (dynamic)
app/(tabs)/home.tsx -> /home (group, no URL segment)
app/_layout.tsx -> Stack / Tabs navigator
Navigate & Params
import { Link, router, useLocalSearchParams } from "expo-router";
<Link href="/posts/42">Open</Link> // declarative
router.push("/posts/42"); // push new screen
router.replace("/login"); // no back
router.back(); // go back
const { id } = useLocalSearchParams<{ id: string }>();
Stack & Tabs Layout
import { Stack, Tabs } from "expo-router";
// app/_layout.tsx
<Stack>
<Stack.Screen name="index" options={{ title: "Home" }} />
</Stack>
// app/(tabs)/_layout.tsx
<Tabs screenOptions={{ tabBarActiveTintColor: "#14b8a6" }}>
<Tabs.Screen name="home" options={{ title: "Home" }} />
</Tabs>
Lists & Performance
Optimized FlatList
<FlatList
data={items}
keyExtractor={(it) => it.id} // stable key, not index
renderItem={({ item }) => <Row item={item} />}
initialNumToRender={10}
windowSize={5}
removeClippedSubviews
ItemSeparatorComponent={Sep}
ListEmptyComponent={<Empty />}
refreshing={loading}
onRefresh={refetch} // pull to refresh
onEndReached={loadMore} // infinite scroll
onEndReachedThreshold={0.5}
/>
const Row = memo(({ item }) => <Text>{item.title}</Text>);
Perf Tips
| Do | Why |
| React.memo rows | Skip re-render of unchanged items |
| useCallback handlers | Stable props for memoized children |
| FlashList (Shopify) | Faster for huge / mixed lists |
| Reanimated | Animations on the UI thread |
Native APIs & Storage
Permissions & Devices
import * as Location from "expo-location";
import { useCameraPermissions, CameraView } from "expo-camera";
await Location.requestForegroundPermissionsAsync();
await Location.getCurrentPositionAsync({});
const [perm, request] = useCameraPermissions();
if (!perm?.granted) await request();
Storage
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as SecureStore from "expo-secure-store";
await AsyncStorage.setItem("k", JSON.stringify(v));
const v = JSON.parse((await AsyncStorage.getItem("k")) ?? "null");
await SecureStore.setItemAsync("token", jwt); // sensitive data
const jwt = await SecureStore.getItemAsync("token");
Common Patterns
Quick Reference
| Task | Approach |
| Fetch + cache data | @tanstack/react-query |
| Global state | zustand / Context |
| Forms | react-hook-form + zod |
| Loading spinner | <ActivityIndicator /> |
| Platform check | Platform.OS === "ios" |
| Screen size | useWindowDimensions() |
| Safe area insets | useSafeAreaInsets() |
| Tailwind styling | nativewind |
| Icons | @expo/vector-icons |
Platform-Specific Code
import { Platform } from "react-native";
const pad = Platform.OS === "ios" ? 20 : 16;
Platform.select({ ios: "SF Pro", android: "Roboto" });
// or use platform file extensions: Button.ios.tsx / Button.android.tsx