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.
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.
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.