React Native course ยท Module 4: State and API data
Context API - Global Treasures
In this lesson4
Context API lets you share data between components without "prop drilling" - passing props through many levels.
Creating a Context
// contexts/TreasureContext.tsx
import { createContext, useContext, useState, useReducer, ReactNode } from 'react';
// Types
interface Treasure {
id: string;
name: string;
value: number;
rarity: 'common' | 'rare' | 'legendary';
}
interface TreasureContextType {
treasures: Treasure[];
totalValue: number;
addTreasure: (treasure: Omit<Treasure, 'id'>) => void;
removeTreasure: (id: string) => void;
clearAll: () => void;
}
// Context with default value
const TreasureContext = createContext<TreasureContextType | undefined>(undefined);
// Provider
export function TreasureProvider({ children }: { children: ReactNode }) {
const [treasures, setTreasures] = useState<Treasure[]>([]);
const totalValue = treasures.reduce((sum, t) => sum + t.value, 0);
const addTreasure = (treasure: Omit<Treasure, 'id'>) => {
const newTreasure: Treasure = {
...treasure,
id: Date.now().toString(),
};
setTreasures(prev => [...prev, newTreasure]);
};
const removeTreasure = (id: string) => {
setTreasures(prev => prev.filter(t => t.id !== id));
};
const clearAll = () => {
setTreasures([]);
};
return (
<TreasureContext.Provider
value={{ treasures, totalValue, addTreasure, removeTreasure, clearAll }}
>
{children}
</TreasureContext.Provider>
);
}
// Custom hook
export function useTreasure() {
const context = useContext(TreasureContext);
if (!context) {
throw new Error('useTreasure must be used within TreasureProvider');
}
return context;
}Usage in the Application
// App.tsx
import { TreasureProvider } from './contexts/TreasureContext';
export default function App() {
return (
<TreasureProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Vault" component={VaultScreen} />
</Stack.Navigator>
</NavigationContainer>
</TreasureProvider>
);
}
// Any component in the tree
function TreasureList() {
const { treasures, totalValue, removeTreasure } = useTreasure();
return (
<View>
<Text>Vault value: {totalValue} gold</Text>
<FlatList
data={treasures}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Pressable onPress={() => removeTreasure(item.id)}>
<Text>{item.name} - {item.value} gold</Text>
</Pressable>
)}
/>
</View>
);
}
// Another component
function AddTreasureForm() {
const { addTreasure } = useTreasure();
const [name, setName] = useState('');
const handleAdd = () => {
addTreasure({ name, value: 100, rarity: 'common' });
setName('');
};
return (
<View>
<TextInput value={name} onChangeText={setName} placeholder="Treasure name" />
<Pressable onPress={handleAdd}>
<Text>Add treasure</Text>
</Pressable>
</View>
);
}Optimization with useMemo and useCallback
import { useMemo, useCallback } from 'react';
export function TreasureProvider({ children }: { children: ReactNode }) {
const [treasures, setTreasures] = useState<Treasure[]>([]);
// Memoizing computed values
const totalValue = useMemo(() => {
return treasures.reduce((sum, t) => sum + t.value, 0);
}, [treasures]);
const legendaryCount = useMemo(() => {
return treasures.filter(t => t.rarity === 'legendary').length;
}, [treasures]);
// Memoizing functions
const addTreasure = useCallback((treasure: Omit<Treasure, 'id'>) => {
setTreasures(prev => [...prev, { ...treasure, id: Date.now().toString() }]);
}, []);
const removeTreasure = useCallback((id: string) => {
setTreasures(prev => prev.filter(t => t.id !== id));
}, []);
// Memoizing context value
const value = useMemo(() => ({
treasures,
totalValue,
legendaryCount,
addTreasure,
removeTreasure,
}), [treasures, totalValue, legendaryCount, addTreasure, removeTreasure]);
return (
<TreasureContext.Provider value={value}>
{children}
</TreasureContext.Provider>
);
}Multiple Contexts
// Separation of concerns
<AuthProvider>
<ThemeProvider>
<TreasureProvider>
<NotificationProvider>
<App />
</NotificationProvider>
</TreasureProvider>
</ThemeProvider>
</AuthProvider>Context API works great for global state like authentication, theme, or settings.
Code for this lesson: App.tsx
import React, { createContext, useContext, useState, ReactNode } from 'react';
import { View, Text, Pressable, FlatList, StyleSheet } from 'react-native';
interface Treasure {
id: string;
name: string;
}
interface TreasureContextType {
treasures: Treasure[];
addTreasure: (name: string) => void;
}
// TODO: create the Context with a default value of undefined
const TreasureContext = createContext<TreasureContextType | undefined>(undefined);
function TreasureProvider({ children }: { children: ReactNode }) {
const [treasures, setTreasures] = useState<Treasure[]>([]);
const addTreasure = (name: string) => {
// TODO: add a new treasure to the array with a unique id (Date.now().toString())
// setTreasures(prev => [...prev, { id: Date.now().toString(), name }]);
};
return (
// TODO: provide the value { treasures, addTreasure } through the Provider
<TreasureContext.Provider value={{ treasures, addTreasure }}>
{children}
</TreasureContext.Provider>
);
}
// TODO: create a custom hook useTreasure that reads the context
// and throws an error when used outside the Provider
function useTreasure() {
const context = useContext(TreasureContext);
if (!context) {
throw new Error('useTreasure must be used within TreasureProvider');
}
return context;
}
function TreasureScreen() {
const { treasures, addTreasure } = useTreasure();
return (
<View style={styles.container}>
<Pressable style={styles.button} onPress={() => addTreasure('Pearl')}>
<Text style={styles.buttonText}>Add pearl</Text>
</Pressable>
<FlatList
data={treasures}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text style={styles.item}>{item.name}</Text>}
/>
</View>
);
}
export default function App() {
return (
<TreasureProvider>
<TreasureScreen />
</TreasureProvider>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 20, backgroundColor: '#1a5276' },
button: {
paddingVertical: 14,
borderRadius: 8,
backgroundColor: '#2980b9',
alignItems: 'center',
marginBottom: 16,
},
buttonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
item: { color: '#aed6f1', fontSize: 18, paddingVertical: 6 },
});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 create a new Context in React?
2. What does Context.Provider do?
Hands-on tasks in the game
- Horizontal ordering
Arrange the correct usage of the useContext hook:
- Code editor
Complete the code for TreasureProvider and the custom hook useTreasure.