Follow Us

CodeWithSabir

  • Contact Us
  • Privacy Policy
  • About
  • Terms & Conditions

All Rights Reserved © 2026

  • Light
  • Dark
Mobile Development

Building Your First React Native App: A Complete Beginner's Guide

Sabir Soft
Sabir Lkhaloufi
  • January 20, 2026
  • 3 min read

Building Your First React Native App: A Complete Beginner's Guide

React Native lets JavaScript developers build native mobile apps without learning Swift or Kotlin. The learning curve is real — mobile is different from the web — but the fundamentals are familiar if you know React.

This guide builds a real news reader app from scratch. You'll learn navigation, data fetching, and how React Native's layout system works.

Setup with Expo

Expo is the fastest way to start. It handles the native build tools so you can focus on JavaScript:

npx create-expo-app NewsReader --template blank-typescript
cd NewsReader
npx expo start

Install the Expo Go app on your phone, scan the QR code, and your app runs on your device. Live reload is instant.

Understanding the Differences from Web React

Before writing code, internalize these key differences:

No HTML elements — use RN primitives:

  • <div> → <View>
  • <p>, <span> → <Text>
  • <img> → <Image>
  • <button> → <TouchableOpacity> or <Pressable>
  • <input> → <TextInput>
  • <ul>/<li> → <FlatList>

No CSS classes — use StyleSheet.create():

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    paddingHorizontal: 16,
  },
})

Flexbox by default — but with flexDirection: 'column' as the default (opposite of web).

No px, em, rem — numbers are density-independent pixels:

fontSize: 16,    // NOT '16px'
marginBottom: 8, // NOT '8px'

Project Structure

NewsReader/
├── app/                    # Expo Router pages
│   ├── _layout.tsx         # Root layout
│   ├── index.tsx           # Home screen
│   └── article/[id].tsx    # Article detail
├── components/
│   ├── ArticleCard.tsx
│   └── LoadingSkeletons.tsx
├── hooks/
│   └── useNews.ts
└── types/
    └── index.ts

Types

// types/index.ts
export interface Article {
  id: string
  title: string
  description: string
  url: string
  urlToImage: string | null
  publishedAt: string
  source: {
    name: string
  }
}

Data Fetching Hook

// hooks/useNews.ts
import { useState, useEffect, useCallback } from 'react'
import type { Article } from '@/types'
 
const API_KEY = process.env.EXPO_PUBLIC_NEWS_API_KEY
const BASE_URL = 'https://newsapi.org/v2'
 
export function useNews(category = 'technology') {
  const [articles, setArticles] = useState<Article[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [refreshing, setRefreshing] = useState(false)
 
  const fetchNews = useCallback(async (isRefresh = false) => {
    if (isRefresh) setRefreshing(true)
    else setLoading(true)
    
    setError(null)
 
    try {
      const res = await fetch(
        `${BASE_URL}/top-headlines?country=us&category=${category}&apiKey=${API_KEY}`
      )
      if (!res.ok) throw new Error('Failed to fetch news')
      
      const data = await res.json()
      setArticles(data.articles)
    } catch (e) {
      setError(e instanceof Error ? e.message : 'Something went wrong')
    } finally {
      setLoading(false)
      setRefreshing(false)
    }
  }, [category])
 
  useEffect(() => {
    fetchNews()
  }, [fetchNews])
 
  return { articles, loading, error, refreshing, refresh: () => fetchNews(true) }
}

Article Card Component

// components/ArticleCard.tsx
import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native'
import { useRouter } from 'expo-router'
import type { Article } from '@/types'
 
interface Props {
  article: Article
}
 
export default function ArticleCard({ article }: Props) {
  const router = useRouter()
 
  return (
    <TouchableOpacity
      style={styles.card}
      onPress={() => router.push(`/article/${encodeURIComponent(article.url)}`)}
      activeOpacity={0.7}
    >
      {article.urlToImage && (
        <Image
          source={{ uri: article.urlToImage }}
          style={styles.image}
          resizeMode="cover"
        />
      )}
      <View style={styles.content}>
        <Text style={styles.source}>{article.source.name}</Text>
        <Text style={styles.title} numberOfLines={2}>
          {article.title}
        </Text>
        <Text style={styles.description} numberOfLines={3}>
          {article.description}
        </Text>
        <Text style={styles.date}>
          {new Date(article.publishedAt).toLocaleDateString()}
        </Text>
      </View>
    </TouchableOpacity>
  )
}
 
const styles = StyleSheet.create({
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    marginBottom: 16,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 8,
    elevation: 3, // Android shadow
    overflow: 'hidden',
  },
  image: {
    width: '100%',
    height: 200,
  },
  content: {
    padding: 16,
  },
  source: {
    fontSize: 11,
    fontWeight: '700',
    color: '#3858F6',
    textTransform: 'uppercase',
    letterSpacing: 0.5,
    marginBottom: 6,
  },
  title: {
    fontSize: 16,
    fontWeight: '700',
    color: '#1a1a1a',
    lineHeight: 22,
    marginBottom: 8,
  },
  description: {
    fontSize: 14,
    color: '#666',
    lineHeight: 20,
    marginBottom: 8,
  },
  date: {
    fontSize: 12,
    color: '#999',
  },
})

Home Screen with FlatList

// app/index.tsx
import { View, FlatList, StyleSheet, Text, ActivityIndicator } from 'react-native'
import { StatusBar } from 'expo-status-bar'
import ArticleCard from '@/components/ArticleCard'
import { useNews } from '@/hooks/useNews'
 
export default function HomeScreen() {
  const { articles, loading, error, refreshing, refresh } = useNews()
 
  if (loading) {
    return (
      <View style={styles.centered}>
        <ActivityIndicator size="large" color="#3858F6" />
      </View>
    )
  }
 
  if (error) {
    return (
      <View style={styles.centered}>
        <Text style={styles.errorText}>{error}</Text>
      </View>
    )
  }
 
  return (
    <View style={styles.container}>
      <StatusBar style="dark" />
      <FlatList
        data={articles}
        keyExtractor={item => item.url}
        renderItem={({ item }) => <ArticleCard article={item} />}
        contentContainerStyle={styles.list}
        refreshing={refreshing}
        onRefresh={refresh}
        showsVerticalScrollIndicator={false}
        ListHeaderComponent={
          <Text style={styles.header}>Tech News</Text>
        }
      />
    </View>
  )
}
 
const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: '#f5f5f5' },
  centered: { flex: 1, alignItems: 'center', justifyContent: 'center' },
  list: { padding: 16 },
  header: {
    fontSize: 28,
    fontWeight: '800',
    color: '#1a1a1a',
    marginBottom: 20,
  },
  errorText: { fontSize: 16, color: '#e53e3e' },
})

Common Mistakes for Web Developers

1. Forgetting flex: 1 on containers. Unlike web where divs have natural height, RN Views need flex: 1 to fill their parent.

2. Using % dimensions. Percentages work but cause inconsistencies across screen sizes. Use Dimensions.get('window') or the useWindowDimensions hook for responsive sizes.

3. Not handling the keyboard. On forms, the keyboard covers inputs. Wrap forms in KeyboardAvoidingView:

import { KeyboardAvoidingView, Platform } from 'react-native'
 
<KeyboardAvoidingView
  behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
  style={{ flex: 1 }}
>
  {/* form content */}
</KeyboardAvoidingView>

4. Testing only on one platform. iOS and Android have real differences in shadows, fonts, gesture handling, and keyboard behavior. Test on both.

Key Takeaways

  • Use Expo for new projects — it eliminates native toolchain complexity
  • React Native uses View, Text, Image — not HTML elements
  • FlatList is the correct component for long scrollable lists — never use map in a ScrollView for large datasets
  • Test on real devices early — simulators don't capture touch feel or keyboard behavior accurately
  • shadowColor/shadowOffset for iOS, elevation for Android shadows — they're different APIs
Popular Blogs
Claude AI vs ChatGPT: An Honest Comparison for Developers
  • April 28, 2026
AI Tools Every Developer Should Be Using in 2026
  • April 20, 2026
Using the Claude API in Real Projects: A Practical Developer Guide
  • April 15, 2026
Prompt Engineering for Developers: Write Prompts That Actually Work
  • April 10, 2026
Categories
AIDevOpsNext.jsMobile DevelopmentWeb Development

Related Posts

Mobile Development
React Native vs Flutter in 2026: Which One Should You Actually Use?
Sabir Khaloufi·Jan 25, 2026
Mobile Development
State Management in React Native: Zustand vs Redux Toolkit
Sabir Khaloufi·Jan 15, 2026
Mobile Development
How to Deploy a React Native App to App Store and Google Play
Sabir Khaloufi·Jan 10, 2026