Skip to main content
This tutorial will guide you through creating your first React Native app, understanding its structure, and making your first changes with Fast Refresh.
Make sure you’ve completed the environment setup before starting this tutorial.

Create a new project

Use the React Native CLI to create a new project:
npx react-native init MyFirstApp
This command creates a new React Native project with the following structure:
MyFirstApp/
├── android/          # Android native code
├── ios/             # iOS native code
├── node_modules/    # JavaScript dependencies
├── App.tsx          # Main app component
├── index.js         # Application entry point
├── app.json         # App configuration
├── package.json     # Project dependencies
└── tsconfig.json    # TypeScript configuration
1

Navigate to your project

cd MyFirstApp
2

Install iOS dependencies (macOS only)

cd ios && pod install && cd ..
This installs CocoaPods dependencies required for iOS.
3

Start Metro bundler

npx react-native start
Metro is React Native’s JavaScript bundler. Keep this terminal open.

Run your app

Open a new terminal and run:
npx react-native run-ios
This will:
  1. Build the native iOS app with Xcode
  2. Launch the iOS Simulator
  3. Install and run your app
Specify a device with --simulator="iPhone 15 Pro"
The first build can take 10-15 minutes as native dependencies compile. Subsequent builds are much faster.

Understanding the project structure

Entry point: index.js

This file registers your app with React Native:
index.js
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

// Register the main component
AppRegistry.registerComponent(appName, () => App);
Key concept: AppRegistry is the JavaScript entry point for all React Native apps. It registers your root component so the native code can load and run it.

Main component: App.tsx

Your app’s root component defines the UI:
App.tsx
import React from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  View,
  useColorScheme,
} from 'react-native';

function App(): React.JSX.Element {
  const isDarkMode = useColorScheme() === 'dark';

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
      <ScrollView contentInsetAdjustmentBehavior="automatic">
        <View style={styles.content}>
          <Text style={styles.title}>Welcome to React Native</Text>
          <Text style={styles.subtitle}>Edit App.tsx to change this screen</Text>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  content: {
    padding: 24,
  },
  title: {
    fontSize: 24,
    fontWeight: '600',
  },
  subtitle: {
    fontSize: 14,
    marginTop: 8,
  },
});

export default App;

Core components explained

SafeAreaView

Renders content within safe area boundaries, avoiding notches and system UI

View

The fundamental container component - equivalent to div in web development

Text

Displays text - all text must be wrapped in <Text> components

ScrollView

Scrollable container for content that exceeds screen bounds

Make your first changes

Let’s build a simple counter app to learn React Native fundamentals:
1

Add state with useState

Import useState and add counter state:
App.tsx
import React, {useState} from 'react';
import {View, Text, StyleSheet, Pressable} from 'react-native';

function App(): React.JSX.Element {
  const [count, setCount] = useState(0);
  
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Counter: {count}</Text>
    </View>
  );
}
2

Add interactive buttons

Use Pressable for touchable buttons:
App.tsx
function App(): React.JSX.Element {
  const [count, setCount] = useState(0);
  
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Counter: {count}</Text>
      
      <View style={styles.buttonRow}>
        <Pressable 
          style={styles.button}
          onPress={() => setCount(count - 1)}
        >
          <Text style={styles.buttonText}>-</Text>
        </Pressable>
        
        <Pressable 
          style={styles.button}
          onPress={() => setCount(count + 1)}
        >
          <Text style={styles.buttonText}>+</Text>
        </Pressable>
      </View>
      
      <Pressable 
        style={[styles.button, styles.resetButton]}
        onPress={() => setCount(0)}
      >
        <Text style={styles.buttonText}>Reset</Text>
      </Pressable>
    </View>
  );
}
3

Style your components

Add styles using StyleSheet.create:
App.tsx
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 48,
    fontWeight: 'bold',
    marginBottom: 40,
    color: '#333',
  },
  buttonRow: {
    flexDirection: 'row',
    gap: 20,
    marginBottom: 20,
  },
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 30,
    paddingVertical: 15,
    borderRadius: 10,
    minWidth: 60,
    alignItems: 'center',
  },
  resetButton: {
    backgroundColor: '#FF3B30',
    minWidth: 120,
  },
  buttonText: {
    color: 'white',
    fontSize: 20,
    fontWeight: '600',
  },
});
4

See live changes

Save the file and watch Fast Refresh update your app instantly!

Complete counter app

Here’s the full code for reference:
import React, {useState} from 'react';
import {View, Text, StyleSheet, Pressable, StatusBar} from 'react-native';

function App(): React.JSX.Element {
  const [count, setCount] = useState(0);

  return (
    <View style={styles.container}>
      <StatusBar barStyle="dark-content" />
      <Text style={styles.title}>Counter: {count}</Text>
      
      <View style={styles.buttonRow}>
        <Pressable 
          style={({pressed}) => [
            styles.button,
            pressed && styles.buttonPressed,
          ]}
          onPress={() => setCount(count - 1)}
        >
          <Text style={styles.buttonText}>-</Text>
        </Pressable>
        
        <Pressable 
          style={({pressed}) => [
            styles.button,
            pressed && styles.buttonPressed,
          ]}
          onPress={() => setCount(count + 1)}
        >
          <Text style={styles.buttonText}>+</Text>
        </Pressable>
      </View>
      
      <Pressable 
        style={({pressed}) => [
          styles.button,
          styles.resetButton,
          pressed && styles.buttonPressed,
        ]}
        onPress={() => setCount(0)}
      >
        <Text style={styles.buttonText}>Reset</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 48,
    fontWeight: 'bold',
    marginBottom: 40,
    color: '#333',
  },
  buttonRow: {
    flexDirection: 'row',
    gap: 20,
    marginBottom: 20,
  },
  button: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 30,
    paddingVertical: 15,
    borderRadius: 10,
    minWidth: 60,
    alignItems: 'center',
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: {width: 0, height: 2},
    shadowOpacity: 0.25,
    shadowRadius: 3.84,
  },
  buttonPressed: {
    opacity: 0.7,
    transform: [{scale: 0.98}],
  },
  resetButton: {
    backgroundColor: '#FF3B30',
    minWidth: 120,
  },
  buttonText: {
    color: 'white',
    fontSize: 20,
    fontWeight: '600',
  },
});

export default App;

Key concepts learned

React Native components vs. HTML

<View>
  <Text>Hello World</Text>
</View>
In React Native, all text must be inside <Text> components. You cannot render raw strings directly in <View> components.

Styling with StyleSheet

React Native uses JavaScript objects for styling, similar to CSS-in-JS:
// StyleSheet.create() provides optimization and validation
const styles = StyleSheet.create({
  container: {
    flex: 1,              // Flexbox: take all available space
    justifyContent: 'center',  // Vertical centering
    alignItems: 'center',      // Horizontal centering
  },
});
Key differences from CSS:
  • Property names use camelCase (backgroundColor not background-color)
  • No units for numeric values (they’re in density-independent pixels)
  • Flexbox is the default layout system
  • Limited subset of CSS properties

Platform-specific code

Write code that adapts to iOS and Android:
import {Platform} from 'react-native';

const styles = StyleSheet.create({
  container: {
    paddingTop: Platform.OS === 'ios' ? 20 : 0,
  },
});

// Or use Platform.select()
const styles = StyleSheet.create({
  container: {
    ...Platform.select({
      ios: {paddingTop: 20},
      android: {paddingTop: 0},
    }),
  },
});

Fast Refresh in action

Fast Refresh automatically reloads your app when you save changes:
1

Component state is preserved

Your counter value stays the same when you edit styling or add new components
2

Full reload when needed

Fast Refresh triggers a full reload if you edit exported components or make syntax errors
3

Manual reload

Press R twice in the terminal running Metro, or shake the device and select “Reload”
To disable Fast Refresh: shake the device → Dev Menu → “Disable Fast Refresh”

Debugging your app

Chrome DevTools

Open the developer menu and select “Debug”:
Press Cmd + D or Device → Shake
Then select “Open Debugger” to use Chrome DevTools.

Console logs

Use standard console methods:
console.log('Current count:', count);
console.warn('This is a warning');
console.error('This is an error');
Logs appear in:
  • Metro bundler terminal
  • Chrome DevTools console
  • Xcode console (iOS)
  • Android Logcat (Android)

Next steps

Run on device

Test your app on a physical iPhone or Android device

Core components

Learn about built-in components like FlatList, Image, and TextInput

Navigation

Add multi-screen navigation with React Navigation

Networking

Fetch data from APIs with fetch() and handle responses

Common issues

Red screens indicate JavaScript errors. Read the error message carefully - it usually points to the exact line causing the issue.Common causes:
  • Syntax errors
  • Undefined variables
  • Incorrect imports
Yellow boxes show warnings that don’t crash the app but should be addressed:
  • Deprecated API usage
  • Performance warnings
  • Accessibility issues
If Metro isn’t connecting:
# Reset Metro cache
npx react-native start --reset-cache
iOS: Clean build folder in Xcode (Shift + Cmd + K)Android: Clean Gradle cache
cd android && ./gradlew clean && cd ..

Build docs developers (and LLMs) love