Learning Roadmap
Suggested order for going through this track. Check off items as you go — your progress is saved locally in this browser, no account needed.
All Topics
1 Components & JSX
A React Native app is built from components — functions that return JSX, a syntax that looks like HTML but compiles to native UI elements like View and Text. Every screen is a tree of components nested inside each other.
import React from 'react';
import { View, Text } from 'react-native';
function Greeting() {
return (
<View>
<Text>Hello, Bryn Flow!</Text>
</View>
);
}
export default Greeting;
Unlike web React, there's no <div> or <span> — you compose native primitives instead.
2 Props
Props (short for properties) are how a parent component passes data down into a child component. They're read-only from the child's perspective, which keeps data flow predictable and easy to trace.
import React from 'react';
import { Text } from 'react-native';
function Greeting({ name }) {
return <Text>Hello, {name}!</Text>;
}
// Usage:
// <Greeting name="Priya" />
export default Greeting;
Destructuring props in the function signature keeps components easy to read at a glance.
3 State with useState
State is data that changes over time and drives what a component renders. The useState hook gives a component its own piece of memory — calling the setter re-renders the component with the new value.
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
function Counter() {
const [count, setCount] = useState(0);
return (
<View>
<Text>Count: {count}</Text>
<Button title="Add" onPress={() => setCount(count + 1)} />
</View>
);
}
export default Counter;
Never mutate state directly (e.g. count++) — always call the setter so React knows to re-render.
4 Side Effects with useEffect
Some work — fetching data, subscribing to an event, starting a timer — needs to happen outside the normal render flow. useEffect runs after render and can clean up after itself when the component unmounts.
import React, { useState, useEffect } from 'react';
import { Text } from 'react-native';
function Clock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(id); // cleanup
}, []); // empty array = run once on mount
return <Text>{time.toLocaleTimeString()}</Text>;
}
export default Clock;
The dependency array controls when the effect re-runs — an empty array means "only on mount."
5 Styling with StyleSheet
React Native styles use JavaScript objects with camelCase property names, not CSS files. StyleSheet.create validates your styles and gives a small performance boost by referencing them by ID under the hood.
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
function Card() {
return (
<View style={styles.card}>
<Text style={styles.title}>Bryn Flow</Text>
</View>
);
}
const styles = StyleSheet.create({
card: { padding: 16, borderRadius: 8, backgroundColor: '#fff' },
title: { fontSize: 18, fontWeight: 'bold', color: '#222' },
});
export default Card;
Styles don't cascade like CSS — each component only receives the styles you explicitly pass it.
6 Layout with Flexbox
React Native lays out screens with Flexbox, defaulting every View to flexDirection: 'column' (the opposite of the web default). It's the primary tool for arranging and sizing elements on any screen size.
import React from 'react';
import { View, StyleSheet } from 'react-native';
function Row() {
return (
<View style={styles.row}>
<View style={[styles.box, { backgroundColor: 'tomato' }]} />
<View style={[styles.box, { backgroundColor: 'gold' }]} />
</View>
);
}
const styles = StyleSheet.create({
row: { flex: 1, flexDirection: 'row', justifyContent: 'space-between' },
box: { width: 60, height: 60 },
});
export default Row;
flex: 1 tells a component to grow and fill the remaining available space.
7 Handling Touches
Since there's no mouse on a phone, React Native provides touchable components instead of click handlers. Pressable is the modern, flexible choice; TouchableOpacity is an older but still common alternative that dims on press.
import React from 'react';
import { Pressable, Text, StyleSheet } from 'react-native';
function LikeButton() {
return (
<Pressable
onPress={() => console.log('Liked!')}
style={({ pressed }) => [styles.btn, pressed && { opacity: 0.6 }]}
>
<Text style={styles.text}>Like</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
btn: { padding: 12, backgroundColor: '#0af', borderRadius: 6 },
text: { color: '#fff', textAlign: 'center' },
});
export default LikeButton;
Pressable's function-as-style lets you react to the live pressed state without extra local state.
8 Lists with FlatList
FlatList efficiently renders long scrollable lists by only mounting the items currently visible on screen, instead of rendering everything up front like a plain .map() would.
import React from 'react';
import { FlatList, Text, View } from 'react-native';
const DATA = [
{ id: '1', name: 'Apples' },
{ id: '2', name: 'Bananas' },
{ id: '3', name: 'Cherries' },
];
function GroceryList() {
return (
<FlatList
data={DATA}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View>
<Text>{item.name}</Text>
</View>
)}
/>
);
}
export default GroceryList;
Always provide a stable keyExtractor — it helps React Native track items efficiently as the list changes.
👉 Full walkthrough: Build a Todo App with FlatList and AsyncStorage
10 Networking with fetch
React Native ships the same fetch API as the browser for making HTTP requests. Combined with async/await, useState, and useEffect, it's the standard way to load data from a server when a screen mounts.
import React, { useState, useEffect } from 'react';
import { Text } from 'react-native';
function UserName() {
const [name, setName] = useState('Loading...');
useEffect(() => {
async function loadUser() {
const res = await fetch('https://api.example.com/user/1');
const data = await res.json();
setName(data.name);
}
loadUser();
}, []);
return <Text>{name}</Text>;
}
export default UserName;
Wrap the request in a try/catch in real apps so network failures don't crash the screen silently.
👉 Full walkthrough: Build a Weather App with fetch and Navigation
11 Forms & TextInput
TextInput is the component for text entry. It's controlled by pairing its value with state and updating that state on every keystroke via the onChangeText callback.
import React, { useState } from 'react';
import { View, TextInput, Text } from 'react-native';
function EmailForm() {
const [email, setEmail] = useState('');
return (
<View>
<TextInput
placeholder="Email address"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
/>
<Text>You typed: {email}</Text>
</View>
);
}
export default EmailForm;
keyboardType swaps in the right on-screen keyboard, improving usability on mobile.
12 Local Storage with AsyncStorage
AsyncStorage is a simple, asynchronous, persistent key-value store for small pieces of data — like a user's preferences or an auth token — that should survive an app restart.
import AsyncStorage from '@react-native-async-storage/async-storage';
async function saveUsername(username) {
await AsyncStorage.setItem('username', username);
}
async function loadUsername() {
const value = await AsyncStorage.getItem('username');
return value; // null if nothing was stored
}
All values are stored as strings, so use JSON.stringify/JSON.parse for objects or arrays.
13 Animations with the Animated API
The Animated API drives smooth, performant animations by interpolating an Animated.Value over time and feeding it into a component's style, rather than re-rendering on every frame.
import React, { useRef, useEffect } from 'react';
import { Animated } from 'react-native';
function FadeInBox() {
const opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}).start();
}, [opacity]);
return (
<Animated.View style={{ opacity, width: 100, height: 100, backgroundColor: '#0af' }} />
);
}
export default FadeInBox;
useNativeDriver: true runs the animation on the native UI thread, keeping it smooth even if JavaScript is busy.
14 Platform-Specific Code
iOS and Android sometimes need different values or behavior — shadows, safe areas, fonts. Platform.OS and Platform.select let you branch logic without maintaining separate components.
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
header: {
paddingTop: Platform.OS === 'ios' ? 44 : 24,
...Platform.select({
ios: { fontFamily: 'Helvetica' },
android: { fontFamily: 'Roboto' },
}),
},
});
For larger differences, you can also create separate Component.ios.js and Component.android.js files — the bundler picks the right one automatically.
Practice Quiz
Test what you just learned about React Native.