React Native course ยท Module 4: State and API data
useState and useReducer - The State Vault
In this lesson3
Welcome to the vault of Atlantis! This is where we store the most precious data of our application. Let's start with the basic state management hooks.
useState - The Basic Vault
import { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
function TreasureCounter() {
// Simple numeric state
const [treasures, setTreasures] = useState(0);
// Object state
const [treasure, setTreasure] = useState({
gold: 0,
pearls: 0,
gems: 0,
});
// Array state
const [collectedItems, setCollectedItems] = useState<string[]>([]);
// Updating a single value
const addTreasure = () => {
setTreasures(prev => prev + 1);
};
// Updating an object (immutability!)
const addGold = () => {
setTreasure(prev => ({
...prev,
gold: prev.gold + 10,
}));
};
// Adding to an array
const collectItem = (item: string) => {
setCollectedItems(prev => [...prev, item]);
};
// Removing from an array
const removeItem = (index: number) => {
setCollectedItems(prev => prev.filter((_, i) => i !== index));
};
return (
<View style={styles.container}>
<Text style={styles.title}>Vault: {treasures} treasures</Text>
<Pressable style={styles.button} onPress={addTreasure}>
<Text style={styles.buttonText}>Add treasure</Text>
</Pressable>
<Text>Gold: {treasure.gold}</Text>
<Pressable onPress={addGold}>
<Text>Collect gold</Text>
</Pressable>
<Text>Items: {collectedItems.join(', ')}</Text>
<Pressable onPress={() => collectItem('Pearl')}>
<Text>Collect pearl</Text>
</Pressable>
</View>
);
}Lazy State Initialization
// For expensive initialization operations
const [data, setData] = useState(() => {
// This function runs only once on the first render
return expensiveComputation();
});
// Example: loading from AsyncStorage
const [settings, setSettings] = useState<Settings | null>(null);
useEffect(() => {
const loadSettings = async () => {
const saved = await AsyncStorage.getItem('settings');
if (saved) {
setSettings(JSON.parse(saved));
}
};
loadSettings();
}, []);useReducer - For Complex State
import { useReducer } from 'react';
// Types
interface TreasureState {
gold: number;
pearls: number;
gems: number;
items: string[];
isLoading: boolean;
error: string | null;
}
type TreasureAction =
| { type: 'ADD_GOLD'; amount: number }
| { type: 'ADD_PEARL' }
| { type: 'ADD_GEM'; count: number }
| { type: 'COLLECT_ITEM'; item: string }
| { type: 'SET_LOADING'; isLoading: boolean }
| { type: 'SET_ERROR'; error: string }
| { type: 'RESET' };
// Reducer
function treasureReducer(state: TreasureState, action: TreasureAction): TreasureState {
switch (action.type) {
case 'ADD_GOLD':
return { ...state, gold: state.gold + action.amount };
case 'ADD_PEARL':
return { ...state, pearls: state.pearls + 1 };
case 'ADD_GEM':
return { ...state, gems: state.gems + action.count };
case 'COLLECT_ITEM':
return { ...state, items: [...state.items, action.item] };
case 'SET_LOADING':
return { ...state, isLoading: action.isLoading };
case 'SET_ERROR':
return { ...state, error: action.error, isLoading: false };
case 'RESET':
return initialState;
default:
return state;
}
}
// Initial state
const initialState: TreasureState = {
gold: 0,
pearls: 0,
gems: 0,
items: [],
isLoading: false,
error: null,
};
// Component
function TreasureVault() {
const [state, dispatch] = useReducer(treasureReducer, initialState);
return (
<View>
<Text>Gold: {state.gold}</Text>
<Text>Pearls: {state.pearls}</Text>
<Pressable onPress={() => dispatch({ type: 'ADD_GOLD', amount: 100 })}>
<Text>Add 100 gold</Text>
</Pressable>
<Pressable onPress={() => dispatch({ type: 'ADD_PEARL' })}>
<Text>Add pearl</Text>
</Pressable>
<Pressable onPress={() => dispatch({ type: 'RESET' })}>
<Text>Reset vault</Text>
</Pressable>
</View>
);
}useState works great for simple state, useReducer for complex logic with many actions.
Code for this lesson: App.tsx
import React, { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
export default function App() {
// TODO: create the treasure counter state with an initial value of 0
// const [treasures, setTreasures] = useState(0);
const addTreasure = () => {
// TODO: increase the counter by 1 using the updater function
// setTreasures(prev => prev + 1);
};
const removeTreasure = () => {
// TODO: decrease the counter by 1, but never go below 0
// setTreasures(prev => (prev > 0 ? prev - 1 : 0));
};
return (
<View style={styles.container}>
{/* TODO: display the current number of treasures instead of 0 */}
<Text style={styles.title}>Treasury: 0 treasures</Text>
<View style={styles.row}>
<Pressable style={styles.button} onPress={removeTreasure}>
<Text style={styles.buttonText}>-</Text>
</Pressable>
<Pressable style={styles.button} onPress={addTreasure}>
<Text style={styles.buttonText}>+</Text>
</Pressable>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#1a5276',
},
title: {
fontSize: 22,
color: '#fff',
marginBottom: 24,
},
row: {
flexDirection: 'row',
gap: 16,
},
button: {
paddingVertical: 14,
paddingHorizontal: 28,
borderRadius: 8,
backgroundColor: '#2980b9',
},
buttonText: {
color: '#fff',
fontSize: 20,
fontWeight: '600',
},
});Docs entries used in this lesson
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. How do you correctly initialize useState with an initial value?
2. How do you correctly update a single field in a state object?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Horizontal ordering
Arrange the correct state update preserving immutability:
- Click in order
Click the elements that create an action for useReducer:
- Code editor
Complete the counter code with add and subtract functions.