Cours React Native · Module 4: État et données d'API
L'API Context - Des trésors globaux
Dans cette leçon4
L'API Context permet de partager des données entre les composants sans « prop drilling » - c'est-à-dire sans faire passer les props à travers de nombreux niveaux.
Créer un 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 avec une valeur par défaut
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>
);
}
// Hook personnalisé
export function useTreasure() {
const context = useContext(TreasureContext);
if (!context) {
throw new Error('useTreasure must be used within TreasureProvider');
}
return context;
}Utilisation dans l'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>
);
}
// N'importe quel composant de l'arbre
function TreasureList() {
const { treasures, totalValue, removeTreasure } = useTreasure();
return (
<View>
<Text>Valeur du coffre : {totalValue} d'or</Text>
<FlatList
data={treasures}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Pressable onPress={() => removeTreasure(item.id)}>
<Text>{item.name} - {item.value} d'or</Text>
</Pressable>
)}
/>
</View>
);
}
// Un autre composant
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="Nom du trésor" />
<Pressable onPress={handleAdd}>
<Text>Ajouter un trésor</Text>
</Pressable>
</View>
);
}Optimisation avec useMemo et useCallback
import { useMemo, useCallback } from 'react';
export function TreasureProvider({ children }: { children: ReactNode }) {
const [treasures, setTreasures] = useState<Treasure[]>([]);
// Mémoïsation des valeurs calculées
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]);
// Mémoïsation des fonctions
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));
}, []);
// Mémoïsation de la valeur du contexte
const value = useMemo(() => ({
treasures,
totalValue,
legendaryCount,
addTreasure,
removeTreasure,
}), [treasures, totalValue, legendaryCount, addTreasure, removeTreasure]);
return (
<TreasureContext.Provider value={value}>
{children}
</TreasureContext.Provider>
);
}Plusieurs Contexts
// Séparation des responsabilités
<AuthProvider>
<ThemeProvider>
<TreasureProvider>
<NotificationProvider>
<App />
</NotificationProvider>
</TreasureProvider>
</ThemeProvider>
</AuthProvider>L'API Context est parfaite pour l'état global comme l'authentification, le thème ou les paramètres.
Code de cette leçon : 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 : crée le Context avec une valeur par défaut undefined
const TreasureContext = createContext<TreasureContextType | undefined>(undefined);
function TreasureProvider({ children }: { children: ReactNode }) {
const [treasures, setTreasures] = useState<Treasure[]>([]);
const addTreasure = (name: string) => {
// TODO : ajoute un nouveau trésor au tableau avec un id unique (Date.now().toString())
// setTreasures(prev => [...prev, { id: Date.now().toString(), name }]);
};
return (
// TODO : fournis la valeur { treasures, addTreasure } via le Provider
<TreasureContext.Provider value={{ treasures, addTreasure }}>
{children}
</TreasureContext.Provider>
);
}
// TODO : crée un hook personnalisé useTreasure qui lit le context
// et qui lève une erreur s'il est utilisé en dehors du 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('Perle')}>
<Text style={styles.buttonText}>Ajouter une perle</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 },
});Entrées de la documentation utilisées dans cette leçon
Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Comment crée-t-on un nouveau Context en React ?
2. Que fait Context.Provider ?
Tâches pratiques dans le jeu
- Tri horizontal
Reconstitue l'utilisation correcte du hook useContext :
- Éditeur de code
Complète le code du TreasureProvider et du hook personnalisé useTreasure.