Setup and your first screen

Create a project, understand the Metro bundler and the bridge, and render your first screen with core React Native components.

Creating a project

React Native does not render HTML. Your JavaScript describes a tree of host components, and the runtime translates that tree into real iOS and Android views. A bundler called Metro serves the JavaScript to the device during development.

# Community CLI: full control over the native projects
npx @react-native-community/cli@latest init Notes --version latest
cd Notes
npx react-native run-android
npx react-native run-ios

# or start with Expo, which manages the native side for you
npx create-expo-app@latest Notes --template blank-typescript
npx expo start
ToolRole
Node.js 18+Runs Metro, the CLI and your tooling
MetroBundles JavaScript and serves it with fast refresh
Android Studio / SDKBuilds and runs the Android app; provides an emulator
XcodeBuilds and runs the iOS app; provides a simulator (macOS only)
Watchman (optional)File watching that stays fast in large repos
Flipper / DevToolsInspect the component tree, network and performance
💡
Use the community CLI when you need native modules or custom native code you control. Use Expo when you want to ship sooner and can stay within its supported modules — you can always eject later with a development build.

The first screen

import React, { useState } from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  TextInput,
  Pressable,
  View,
} from 'react-native';

export default function App() {
  const [draft, setDraft] = useState('');
  const [notes, setNotes] = useState(['Buy milk', 'Write summary']);

  function addNote() {
    const title = draft.trim();
    if (!title) return;
    setNotes([...notes, title]);
    setDraft('');
  }

  return (
    <SafeAreaView style={styles.screen}>
      <StatusBar barStyle="dark-content" />
      <Text style={styles.heading}>Notes</Text>

      <View style={styles.row}>
        <TextInput
          style={styles.input}
          value={draft}
          onChangeText={setDraft}
          placeholder="New note"
          onSubmitEditing={addNote}
        />
        <Pressable style={styles.button} onPress={addNote}>
          <Text style={styles.buttonLabel}>Add</Text>
        </Pressable>
      </View>

      <ScrollView>
        {notes.map((note, index) => (
          <Text key={index} style={styles.note}>
            {note}
          </Text>
        ))}
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  screen: { flex: 1, padding: 20, backgroundColor: '#fff' },
  heading: { fontSize: 28, fontWeight: '700', marginBottom: 12 },
  row: { flexDirection: 'row', gap: 8, marginBottom: 16 },
  input: { flex: 1, borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 10 },
  button: { justifyContent: 'center', paddingHorizontal: 16, backgroundColor: '#3b5bdb', borderRadius: 8 },
  buttonLabel: { color: '#fff', fontWeight: '600' },
  note: { paddingVertical: 8, fontSize: 16 },
});
  • Every string must be inside <Text>. Raw text in a <View> throws, unlike on the web.
  • StyleSheet.create gives you a stable style object, validation of property names, and no re-allocation on each render.
  • Fast Refresh keeps component state while you edit, but changing a module outside the component tree triggers a full reload.

FAQ

Do I need a Mac to build for iOS?
Yes for local builds: Xcode only runs on macOS. Cloud build services such as EAS Build can produce iOS binaries for you without one.
Why is the app blank on the device?
Almost always a bundler or connection problem: check that Metro is running, that the device can reach your machine on the same network, and read the Metro terminal for a red-box error before assuming a layout bug.

Core components and styling Navigation, native modules and Expo

Last refreshed 2026-09-18.