# React Native: Een complete mobiele app bouwen in 2026
> Uitgebreide gids voor het ontwikkelen van iOS- en Android-apps met React Native. Van setup tot deployment -- alle basisprincipes om te starten.
- Published: 2026-01-10
- Updated: 2026-04-06
- Author: SharpSkill
- Tags: react native, mobile development, javascript, ios, android
- Reading time: 15 min
---
React Native maakt het mogelijk om native mobiele applicaties voor iOS en Android te bouwen vanuit een enkele JavaScript-codebase. Het framework wordt onderhouden door Meta en ingezet door apps als Instagram, Facebook en Discord. Het combineert snelle ontwikkelcycli met goed presterende applicaties.
> **Waarom React Native in 2026?**
>
> De nieuwe architectuur (Fabric en TurboModules) is inmiddels stabiel en levert nagenoeg native prestaties met behoud van hoge ontwikkelproductiviteit. Meer dan 40% van de 500 populairste apps maakt gebruik van React Native.
## Development Environment Setup
Voordat men begint, moeten de benodigde tools worden geinstalleerd. React Native biedt twee benaderingen: Expo (aanbevolen voor beginners) en de React Native CLI (voor meer controle).
```bash
# setup.sh
# Node.js installation (LTS version recommended)
# Check installed version
node --version # >= 18.x required
npm --version # >= 9.x required
# Install Expo CLI tool globally
npm install -g expo-cli
# Create a new project with Expo
npx create-expo-app@latest MyApp --template blank-typescript
# Navigate to the project
cd MyApp
# Start the development server
npx expo start
```
Expo vereenvoudigt het ontwikkelproces aanzienlijk door de native configuratie automatisch af te handelen. Om de app te testen volstaat de Expo Go-app op een smartphone -- de weergegeven QR-code wordt eenvoudig gescand.
```bash
# structure.sh
# Generated project structure
MyApp/
├── App.tsx # Application entry point
├── app.json # Expo configuration
├── package.json # Dependencies
├── tsconfig.json # TypeScript configuration
├── babel.config.js # Babel configuration
└── assets/ # Images and resources
├── icon.png
└── splash.png
```
## Core React Native Components
React Native biedt native componenten die HTML-elementen vervangen. Elk component wordt rechtstreeks vertaald naar een native iOS- of Android-component.
```tsx
// App.tsx
import React from 'react';
import {
View,
Text,
StyleSheet,
SafeAreaView,
StatusBar
} from 'react-native';
// Main application component
export default function App() {
return (
// SafeAreaView prevents overlap with status bar
{/* StatusBar configures system bar appearance */}
{/* View is the equivalent of div */}
{/* Text is required to display text */}
Welcome to MyApp
A React Native application
);
}
// StyleSheet.create optimizes styles for native
const styles = StyleSheet.create({
container: {
flex: 1, // Takes all available space
backgroundColor: '#ffffff',
},
header: {
padding: 20,
alignItems: 'center', // Centers horizontally
justifyContent: 'center', // Centers vertically
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#1a1a1a',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: '#666666',
},
});
```
> **Flexbox als standaard**
>
> React Native gebruikt Flexbox met standaard `flexDirection: 'column'` (in tegenstelling tot het web dat `row` hanteert). Dit verschil is cruciaal bij het opzetten van layouts.
## Handling User Interactions
Touch-interacties worden beheerd via gespecialiseerde componenten. Elk interactietype heeft een eigen component dat is geoptimaliseerd voor prestaties.
```tsx
// components/InteractiveButton.tsx
import React, { useState } from 'react';
import {
TouchableOpacity,
TouchableHighlight,
Pressable,
Text,
StyleSheet,
View,
} from 'react-native';
// Button component with different interaction styles
export function InteractiveButton() {
const [count, setCount] = useState(0);
return (
{/* TouchableOpacity reduces opacity on touch */}
setCount(c => c + 1)}
>
Counter: {count}
{/* Pressable offers more control over states */}
[
styles.button,
styles.pressableButton,
pressed && styles.buttonPressed,
]}
onPress={() => console.log('Pressed!')}
onLongPress={() => console.log('Long press!')}
>
{({ pressed }) => (
{pressed ? 'Pressed!' : 'Press here'}
)}
);
}
const styles = StyleSheet.create({
container: {
gap: 16,
padding: 20,
},
button: {
backgroundColor: '#007AFF',
paddingVertical: 14,
paddingHorizontal: 28,
borderRadius: 10,
alignItems: 'center',
},
pressableButton: {
backgroundColor: '#34C759',
},
buttonPressed: {
backgroundColor: '#2DA44E',
transform: [{ scale: 0.98 }],
},
buttonText: {
color: '#ffffff',
fontSize: 16,
fontWeight: '600',
},
});
```
## Screen Navigation with React Navigation
Navigatie is onmisbaar in elke mobiele app. React Navigation is de standaardoplossing en biedt verschillende navigatietypen die zijn afgestemd op mobiele patronen.
```bash
# install-navigation.sh
# Installing navigation dependencies
npx expo install @react-navigation/native
npx expo install @react-navigation/native-stack
npx expo install react-native-screens react-native-safe-area-context
```
```tsx
// App.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
// Screen imports
import { HomeScreen } from './screens/HomeScreen';
import { DetailScreen } from './screens/DetailScreen';
import { ProfileScreen } from './screens/ProfileScreen';
// Type definition for TypeScript navigation
export type RootStackParamList = {
Home: undefined; // No parameters
Detail: { itemId: number; title: string }; // Required parameters
Profile: { userId?: string }; // Optional parameter
};
// Creating typed navigator
const Stack = createNativeStackNavigator();
export default function App() {
return (
// NavigationContainer manages navigation state
({
title: route.params.title
})}
/>
);
}
```
```tsx
// screens/HomeScreen.tsx
import React from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet } from 'react-native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { RootStackParamList } from '../App';
// Typing navigation props
type Props = NativeStackScreenProps;
// Sample data
const ITEMS = [
{ id: 1, title: 'First item' },
{ id: 2, title: 'Second item' },
{ id: 3, title: 'Third item' },
];
export function HomeScreen({ navigation }: Props) {
return (
{/* FlatList for performant lists */}
item.id.toString()}
renderItem={({ item }) => (
{
// Navigation with typed parameters
navigation.navigate('Detail', {
itemId: item.id,
title: item.title,
});
}}
>
{item.title}
)}
ItemSeparatorComponent={() => }
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
item: {
backgroundColor: '#ffffff',
padding: 20,
},
itemText: {
fontSize: 16,
color: '#1a1a1a',
},
separator: {
height: 1,
backgroundColor: '#e0e0e0',
},
});
```
## State Management with Context and Hooks
Voor eenvoudig tot gemiddeld state management biedt React Context in combinatie met hooks een doeltreffende oplossing zonder externe afhankelijkheden.
```tsx
// context/AuthContext.tsx
import React, { createContext, useContext, useState, useCallback } from 'react';
// Types for authentication
interface User {
id: string;
email: string;
name: string;
}
interface AuthContextType {
user: User | null;
isLoading: boolean;
signIn: (email: string, password: string) => Promise;
signOut: () => void;
}
// Creating context with default value
const AuthContext = createContext(undefined);
// Provider that wraps the application
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// Sign in function
const signIn = useCallback(async (email: string, password: string) => {
setIsLoading(true);
try {
// Simulated API call
const response = await fetch('https://api.example.com/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
setUser(data.user);
} catch (error) {
console.error('Login error:', error);
throw error;
} finally {
setIsLoading(false);
}
}, []);
// Sign out function
const signOut = useCallback(() => {
setUser(null);
}, []);
return (
{children}
);
}
// Custom hook to use the context
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}
```
```tsx
// screens/LoginScreen.tsx
import React, { useState } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
ActivityIndicator,
Alert,
} from 'react-native';
import { useAuth } from '../context/AuthContext';
export function LoginScreen() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const { signIn, isLoading } = useAuth();
// Form submission handler
const handleSubmit = async () => {
if (!email || !password) {
Alert.alert('Error', 'Please fill in all fields');
return;
}
try {
await signIn(email, password);
} catch (error) {
Alert.alert('Error', 'Invalid credentials');
}
};
return (
Login
{isLoading ? (
) : (
Sign In
)}
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
justifyContent: 'center',
backgroundColor: '#ffffff',
},
title: {
fontSize: 28,
fontWeight: 'bold',
marginBottom: 32,
textAlign: 'center',
},
input: {
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 10,
padding: 16,
marginBottom: 16,
fontSize: 16,
backgroundColor: '#f9f9f9',
},
button: {
backgroundColor: '#007AFF',
padding: 16,
borderRadius: 10,
alignItems: 'center',
marginTop: 8,
},
buttonDisabled: {
backgroundColor: '#99c9ff',
},
buttonText: {
color: '#ffffff',
fontSize: 18,
fontWeight: '600',
},
});
```
> **Globale vs. lokale state**
>
> Bij complexere applicaties met uitgebreide gedeelde state kunnen oplossingen als Zustand of Redux Toolkit beter geschikt zijn. Context is optimaal voor state die zelden verandert (thema, authenticatie).
## API Calls and Data Management
Communicatie met de backend vormt een kernonderdeel van elke mobiele app. Het volgende patroon maakt gebruik van een abstractielaag voor robuuste API-aanroepen.
```tsx
// services/api.ts
// Base API configuration
const API_BASE_URL = 'https://api.example.com';
// Type for API errors
interface ApiError {
message: string;
code: string;
status: number;
}
// Utility function for requests
async function request(
endpoint: string,
options: RequestInit = {}
): Promise {
const url = `${API_BASE_URL}${endpoint}`;
const config: RequestInit = {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
};
try {
const response = await fetch(url, config);
if (!response.ok) {
const error: ApiError = await response.json();
throw new Error(error.message || 'An error occurred');
}
return response.json();
} catch (error) {
// Network error handling
if (error instanceof TypeError) {
throw new Error('Network connection issue');
}
throw error;
}
}
// Types for entities
interface Product {
id: string;
name: string;
price: number;
description: string;
imageUrl: string;
}
// Products service
export const productApi = {
// Get all products
getAll: () => request('/products'),
// Get product by ID
getById: (id: string) => request(`/products/${id}`),
// Create a product
create: (data: Omit) =>
request('/products', {
method: 'POST',
body: JSON.stringify(data),
}),
// Update a product
update: (id: string, data: Partial) =>
request(`/products/${id}`, {
method: 'PATCH',
body: JSON.stringify(data),
}),
};
```
```tsx
// hooks/useProducts.ts
import { useState, useEffect, useCallback } from 'react';
import { productApi } from '../services/api';
interface Product {
id: string;
name: string;
price: number;
description: string;
imageUrl: string;
}
// Custom hook for product management
export function useProducts() {
const [products, setProducts] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
// Initial products loading
const fetchProducts = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const data = await productApi.getAll();
setProducts(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setIsLoading(false);
}
}, []);
// Refresh (pull-to-refresh)
const refresh = useCallback(async () => {
await fetchProducts();
}, [fetchProducts]);
// Load on mount
useEffect(() => {
fetchProducts();
}, [fetchProducts]);
return {
products,
isLoading,
error,
refresh,
};
}
```
```tsx
// screens/ProductListScreen.tsx
import React from 'react';
import {
View,
Text,
FlatList,
Image,
StyleSheet,
RefreshControl,
ActivityIndicator,
} from 'react-native';
import { useProducts } from '../hooks/useProducts';
export function ProductListScreen() {
const { products, isLoading, error, refresh } = useProducts();
// Display during initial loading
if (isLoading && products.length === 0) {
return (
Loading...
);
}
// Display on error
if (error) {
return (
{error}
);
}
return (
item.id}
contentContainerStyle={styles.list}
// Pull-to-refresh
refreshControl={
}
renderItem={({ item }) => (
{item.name}
${item.price.toFixed(2)}
)}
/>
);
}
const styles = StyleSheet.create({
centered: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
loadingText: {
marginTop: 12,
fontSize: 16,
color: '#666666',
},
errorText: {
fontSize: 16,
color: '#FF3B30',
textAlign: 'center',
},
list: {
padding: 16,
},
card: {
backgroundColor: '#ffffff',
borderRadius: 12,
marginBottom: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
image: {
width: '100%',
height: 200,
borderTopLeftRadius: 12,
borderTopRightRadius: 12,
},
cardContent: {
padding: 16,
},
productName: {
fontSize: 18,
fontWeight: '600',
color: '#1a1a1a',
marginBottom: 4,
},
productPrice: {
fontSize: 16,
color: '#007AFF',
fontWeight: '500',
},
});
```
## Local Data Storage
Lokale opslag maakt het mogelijk om gegevens tussen sessies te bewaren. AsyncStorage is de standaardoplossing voor eenvoudige data, terwijl SQLite geschikt is voor complexe gestructureerde gegevens.
```tsx
// services/storage.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
// Required installation: npx expo install @react-native-async-storage/async-storage
// Centralized storage keys
const STORAGE_KEYS = {
USER_TOKEN: '@app/user_token',
USER_PREFERENCES: '@app/user_preferences',
ONBOARDING_COMPLETE: '@app/onboarding_complete',
} as const;
// Types for user preferences
interface UserPreferences {
theme: 'light' | 'dark' | 'system';
notifications: boolean;
language: string;
}
// Typed storage service
export const storage = {
// Authentication token
async getToken(): Promise {
return AsyncStorage.getItem(STORAGE_KEYS.USER_TOKEN);
},
async setToken(token: string): Promise {
await AsyncStorage.setItem(STORAGE_KEYS.USER_TOKEN, token);
},
async removeToken(): Promise {
await AsyncStorage.removeItem(STORAGE_KEYS.USER_TOKEN);
},
// User preferences (JSON object)
async getPreferences(): Promise {
const data = await AsyncStorage.getItem(STORAGE_KEYS.USER_PREFERENCES);
return data ? JSON.parse(data) : null;
},
async setPreferences(prefs: UserPreferences): Promise {
await AsyncStorage.setItem(
STORAGE_KEYS.USER_PREFERENCES,
JSON.stringify(prefs)
);
},
// Onboarding
async isOnboardingComplete(): Promise {
const value = await AsyncStorage.getItem(STORAGE_KEYS.ONBOARDING_COMPLETE);
return value === 'true';
},
async setOnboardingComplete(): Promise {
await AsyncStorage.setItem(STORAGE_KEYS.ONBOARDING_COMPLETE, 'true');
},
// Complete cleanup
async clearAll(): Promise {
const keys = Object.values(STORAGE_KEYS);
await AsyncStorage.multiRemove(keys);
},
};
```
> **Beveiliging van gevoelige gegevens**
>
> Voor gevoelige gegevens zoals authenticatietokens is het raadzaam expo-secure-store te gebruiken. Dit pakket versleutelt data via iOS Keychain en Android Keystore.
## Responsive Styles and Theming
Een professionele applicatie moet zich aanpassen aan verschillende schermformaten en ondersteuning bieden voor de donkere modus.
```tsx
// theme/index.ts
import { Dimensions, PixelRatio, Platform } from 'react-native';
const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window');
// Reference dimensions (iPhone 14)
const guidelineBaseWidth = 390;
const guidelineBaseHeight = 844;
// Scaling functions
export const scale = (size: number) =>
(SCREEN_WIDTH / guidelineBaseWidth) * size;
export const verticalScale = (size: number) =>
(SCREEN_HEIGHT / guidelineBaseHeight) * size;
export const moderateScale = (size: number, factor = 0.5) =>
size + (scale(size) - size) * factor;
// Light theme
export const lightTheme = {
colors: {
primary: '#007AFF',
secondary: '#5856D6',
success: '#34C759',
warning: '#FF9500',
error: '#FF3B30',
background: '#FFFFFF',
surface: '#F2F2F7',
text: '#000000',
textSecondary: '#8E8E93',
border: '#E5E5EA',
},
spacing: {
xs: scale(4),
sm: scale(8),
md: scale(16),
lg: scale(24),
xl: scale(32),
},
typography: {
h1: {
fontSize: moderateScale(32),
fontWeight: 'bold' as const,
lineHeight: moderateScale(40),
},
h2: {
fontSize: moderateScale(24),
fontWeight: 'bold' as const,
lineHeight: moderateScale(32),
},
body: {
fontSize: moderateScale(16),
lineHeight: moderateScale(24),
},
caption: {
fontSize: moderateScale(14),
lineHeight: moderateScale(20),
},
},
borderRadius: {
sm: scale(4),
md: scale(8),
lg: scale(12),
full: 9999,
},
};
// Dark theme
export const darkTheme = {
...lightTheme,
colors: {
...lightTheme.colors,
background: '#000000',
surface: '#1C1C1E',
text: '#FFFFFF',
textSecondary: '#8E8E93',
border: '#38383A',
},
};
export type Theme = typeof lightTheme;
```
```tsx
// context/ThemeContext.tsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import { useColorScheme } from 'react-native';
import { lightTheme, darkTheme, Theme } from '../theme';
import { storage } from '../services/storage';
interface ThemeContextType {
theme: Theme;
isDark: boolean;
toggleTheme: () => void;
setThemeMode: (mode: 'light' | 'dark' | 'system') => void;
}
const ThemeContext = createContext(undefined);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const systemColorScheme = useColorScheme();
const [themeMode, setThemeMode] = useState<'light' | 'dark' | 'system'>('system');
// Determine effective theme
const isDark =
themeMode === 'system'
? systemColorScheme === 'dark'
: themeMode === 'dark';
const theme = isDark ? darkTheme : lightTheme;
// Load preferences on startup
useEffect(() => {
storage.getPreferences().then((prefs) => {
if (prefs?.theme) {
setThemeMode(prefs.theme);
}
});
}, []);
// Toggle between light and dark
const toggleTheme = () => {
const newMode = isDark ? 'light' : 'dark';
setThemeMode(newMode);
storage.getPreferences().then((prefs) => {
storage.setPreferences({
...prefs,
theme: newMode,
notifications: prefs?.notifications ?? true,
language: prefs?.language ?? 'en',
});
});
};
return (
{children}
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
```
## Preparing for Publication
Voor het publiceren van een app zijn verschillende configuratie- en optimalisatiestappen nodig.
```json
// app.json
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#007AFF"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.example.myapp",
"buildNumber": "1"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#007AFF"
},
"package": "com.example.myapp",
"versionCode": 1
},
"plugins": [
"expo-router"
]
}
}
```
```bash
# build.sh
# EAS Build configuration (Expo Application Services)
npm install -g eas-cli
# Log in to Expo account
eas login
# Configure the project
eas build:configure
# Build for iOS (simulator)
eas build --platform ios --profile development
# Build for Android (test APK)
eas build --platform android --profile preview
# Production build
eas build --platform all --profile production
# Store submission
eas submit --platform ios
eas submit --platform android
```
> **Certificaten en sleutels**
>
> Voor publicatie op iOS is een Apple Developer-account vereist (99 $/jaar) en voor Android een Google Play Console-account (eenmalig 25 $). EAS beheert ondertekeningscertificaten automatisch.
## Conclusion
React Native biedt een efficiente aanpak voor de ontwikkeling van cross-platform mobiele apps. De basisprincipes uit deze gids stellen ontwikkelaars in staat om volledige en professionele applicaties te bouwen.
### Checklist voor een geslaagde React Native-app
- De ontwikkelomgeving opzetten met Expo voor een snelle start
- Kerncomponenten beheersen: View, Text, TouchableOpacity, FlatList
- Navigatie implementeren met React Navigation en TypeScript-typering
- State beheren met de Context API en custom hooks
- API-aanroepen structureren met een dedicated servicelaag
- Gegevens lokaal persisteren met AsyncStorage
- Een responsief themasysteem opzetten met ondersteuning voor de donkere modus
De officiele documentatie van React Native en Expo blijft het referentiepunt om elk onderwerp te verdiepen. Het ecosysteem evolueert snel en bibliotheken als React Query of Zustand kunnen het databeheer in complexere applicaties verder vereenvoudigen.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/nl/blog/react-native/react-native-building-complete-mobile-app