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.

Create a free account to save this page, take notes and track your progress. Sign in

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-app to 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:

The counter's logic, without the UITry it yourself
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 use onPress.
  • Forgetting accessibility. Give pressables an accessibilityRole and labels so screen readers work.
  • Installing libraries with plain npm install for native modules. Use npx 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, ScrollView replace div, 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.

Was this page helpful?