Mobile Apps with React Native and Expo · Foundations: Screens, Styles, Lists and Navigation
Your First App with React Native and Expo
What React Native is (real native views driven by JavaScript), why Expo is the recommended way to start, and how to create, run and change an app on your own phone in minutes.
What you'll build
A "Hello" screen running on your own phone, with a button that counts taps. You'll create the project, open it in Expo Go, and see changes appear instantly when you save.
The idea
React Native lets you build iOS and Android apps with React and TypeScript. Unlike a website in a wrapper, your components become real native views: a <Text> is a native text view, a <ScrollView> scrolls like any other app. Your JavaScript runs on the device and tells the native side what to draw.
Expo is the framework the React Native team recommends for new apps. It gives you:
create-expo-appto start a project with sensible defaults,- Expo Go, an app for your phone that runs your project without building it in Xcode or Android Studio,
- the
expo-*libraries for camera, notifications, secure storage, files and more, - Expo Router for file-based navigation, and EAS to build and publish to the app stores.
The mental model you already know from React still applies: components are functions, state changes cause re-renders, and props flow down.
| Web (React DOM) | React Native |
|---|---|
<div> |
<View> |
<p>, <span> |
<Text> (all text must be inside <Text>) |
<img> |
<Image> (or expo-image) |
<button onClick> |
<Pressable onPress> |
| CSS files, classes | StyleSheet.create({...}) objects |
<input> |
<TextInput> |
| scrolling page | <ScrollView> / <FlatList> |
The code
Create and start a project (Node.js required):
npx create-expo-app@latest my-app
cd my-app
npx expo start # scan the QR code with Expo Go (Android) or the Camera app (iOS)
Replace the home screen (app/index.tsx in the default template) with:
import { useState } from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
export default function Home() {
const [taps, setTaps] = useState(0);
return (
<View style={styles.screen}>
<Text style={styles.title}>Hello from my phone</Text>
<Pressable
onPress={() => setTaps((n) => n + 1)}
style={({ pressed }) => [styles.button, pressed && styles.pressed]}
accessibilityRole="button"
>
<Text style={styles.buttonText}>Tapped {taps} {taps === 1 ? "time" : "times"}</Text>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
screen: { flex: 1, alignItems: "center", justifyContent: "center", gap: 16 },
title: { fontSize: 24, fontWeight: "600" },
button: { backgroundColor: "#0f766e", paddingVertical: 12, paddingHorizontal: 20, borderRadius: 10 },
pressed: { opacity: 0.7 },
buttonText: { color: "white", fontSize: 16 },
});
Save the file and the app updates on your phone in about a second (Fast Refresh), keeping the current state.
Try the logic
UI code needs a device, but the logic inside components is plain TypeScript. Here's the label logic from the button, plus the state update pattern, running on its own:
function label(taps: number): string {
return `Tapped ${taps} ${taps === 1 ? "time" : "times"}`;
}
// setTaps((n) => n + 1) passes an updater function: React gives it the latest value.
type Updater = (n: number) => number;
let taps = 0;
const setTaps = (update: Updater) => { taps = update(taps); };
console.log(label(taps));
setTaps((n) => n + 1);
console.log(label(taps));
setTaps((n) => n + 1);
setTaps((n) => n + 1);
console.log(label(taps));
Output
Tapped 0 times Tapped 1 time Tapped 3 times
Keeping logic in small pure functions like label is a habit worth building early: they're trivial to test and reuse across screens.
Common mistakes
- Text outside
<Text>.<View>Hello</View>crashes; wrap all strings in<Text>. - Expecting CSS. There are no class names, no cascade and no units: numbers are density-independent pixels.
- Using
onClick. Touchable components useonPress. - Forgetting accessibility. Give pressables an
accessibilityRoleand labels so screen readers work. - Installing libraries with plain
npm installfor native modules. Usenpx expo install <package>, which picks versions compatible with your Expo SDK.
Key points
- React Native renders real native views from React components.
- Expo is the recommended starting point: Expo Go,
expo-*libraries, Expo Router and EAS. View,Text,Pressable,TextInput,ScrollViewreplacediv,p,button,input.- Keep logic in plain TypeScript functions; it's easy to test.
Further reading: Expo documentation, React Native core components.
Practice questions
How is React Native different from a web app inside a WebView?
Answer
React Native components render real native platform views (UIKit on iOS, Android Views), driven by JavaScript running on the device, so the app looks and scrolls like any native app. A WebView app renders HTML/CSS in an embedded browser.
Why use `npx expo install` instead of `npm install` for some packages?
Answer
Native modules must match the React Native version in your Expo SDK. npx expo install picks the compatible version for your SDK (and falls back to your package manager for plain JS packages).
Open in the app to mark lessons complete, take notes and track your progress.