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| Tool | Role |
|---|---|
| Node.js 18+ | Runs Metro, the CLI and your tooling |
| Metro | Bundles JavaScript and serves it with fast refresh |
| Android Studio / SDK | Builds and runs the Android app; provides an emulator |
| Xcode | Builds and runs the iOS app; provides a simulator (macOS only) |
| Watchman (optional) | File watching that stays fast in large repos |
| Flipper / DevTools | Inspect 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.creategives 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.
Related
Core components and styling Navigation, native modules and Expo
Last refreshed 2026-09-18.