Exercices Pratiques - Séance 4
Mettez en pratique vos connaissances sur useEffect et React Router avec ces exercices progressifs.
Comment travailler efficacement
- Créez un projet Vite :
npm create vite@latest react-exercises -- --template react-ts - Installez React Router :
npm install react-router-dom - Testez régulièrement : Lancez
npm run devet vérifiez dans le navigateur - Essayez d'abord : Tentez de résoudre avant de regarder les indices
- Comprenez le code : L'objectif est de comprendre, pas de copier !
Temps estimé total : 3-4 heures pour les 5 exercices
Exercice 1 : Simple Data Fetch
Difficulté : Facile
Durée estimée : 15-20 minutes
Objectif : Récupérer et afficher une liste d'utilisateurs depuis une API.
Consignes
Créez un composant UserList qui :
- Récupère les utilisateurs depuis :
https://jsonplaceholder.typicode.com/users - Affiche un état de chargement (spinner ou texte "Chargement...")
- Affiche la liste des utilisateurs avec :
- Nom
- Téléphone
- Gère les erreurs avec un message d'erreur
- Ajoute un bouton "Rafraîchir" pour re-fetch les données
Structure HTML Suggérée
<div>
<h1>Liste des Utilisateurs</h1>
<button onClick={handleRefresh}>Rafraîchir</button>
{loading && <p>Chargement...</p>}
{error && <p style={{color: 'red'}}>Erreur : {error}</p>}
{!loading && !error && (
<ul>
{users.map(user => (
<li key={user.id}>
<h3>{user.name}</h3>
<p>Email : {user.email}</p>
<p>Tél : {user.phone}</p>
</li>
))}
</ul>
)}
</div>
Points Clés
- Utiliser
useStatepourusers,loading, eterror - Utiliser
useEffectavec tableau vide[]pour fetch au mount - Pattern
try/catch/finallypour gérer les erreurs - Bouton refresh : changer une dépendance pour re-trigger l'effect
Astuce
Pour le bouton "Rafraîchir", vous pouvez utiliser un compteur :
const [refreshKey, setRefreshKey] = useState(0);
useEffect(() => {
// fetch...
}, [refreshKey]);
function handleRefresh() {
setRefreshKey(k => k + 1);
}
Exercice 2 : Blog Multi-Pages
Difficulté : Moyenne
Durée estimée : 45-60 minutes
Objectif : Créer une application blog avec plusieurs pages et routing.
Consignes
Créez une application avec 4 pages :
Page 1 : Accueil (/)
- Titre de bienvenue
- Lien vers la liste des posts
- Lien vers la page "À propos"
Page 2 : Liste des Posts (/posts)
- Fetch des posts depuis :
https://jsonplaceholder.typicode.com/posts - Afficher les 10 premiers posts
- Chaque post est cliquable et mène vers
/posts/:id - Lien "Retour à l'accueil"
Page 3 : Détail d'un Post (/posts/:id)
- Fetch du post spécifique :
https://jsonplaceholder.typicode.com/posts/:id - Afficher le titre et le contenu
- Lien "Retour à la liste des posts"
- État de chargement pendant le fetch
Page 4 : À Propos (/about)
- Contenu statique (texte de présentation)
- Lien "Retour à l'accueil"
Page 404 : Non Trouvée (*)
- Message "Page non trouvée"
- Lien vers l'accueil
Structure des Routes
<Routes>
<Route path="/" element={<Home />} />
<Route path="/posts" element={<PostList />} />
<Route path="/posts/:id" element={<PostDetail />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>
Points Clés
- Utiliser
<Link>pour la navigation useParams()pour récupérer l'ID du postuseEffectavec[postId]en dépendance- Gérer les états de chargement
- Navigation commune (Header) en dehors de
<Routes>
Bonus
Améliorations possibles :
- Ajouter une barre de navigation commune à toutes les pages
- Utiliser
<NavLink>avec un style actif - Limiter la longueur du contenu affiché dans la liste
- Ajouter un compteur de posts
Exercice 3 : Annuaire Utilisateurs
Difficulté : Moyenne
Durée estimée : 60-75 minutes
Objectif : Créer un annuaire avec recherche et pages de profil.
Consignes
Créez une application avec les fonctionnalités suivantes :
Page : Liste des Utilisateurs (/)
- Fetch tous les utilisateurs :
https://jsonplaceholder.typicode.com/users - Barre de recherche pour filtrer par nom (recherche locale, pas API)
- Afficher les utilisateurs filtrés
- Chaque utilisateur est cliquable → mène vers
/users/:id
Page : Profil Utilisateur (/users/:id)
- Fetch l'utilisateur :
https://jsonplaceholder.typicode.com/users/:id - Fetch ses posts :
https://jsonplaceholder.typicode.com/posts?userId=:id - Afficher :
- Nom, email, téléphone, site web
- Liste de ses posts (titre cliquable)
- Navigation :
- Lien "Retour à l'annuaire"
- Cliquer sur un post mène vers
/posts/:postId
Page : Détail d'un Post (/posts/:id)
- Fetch le post :
https://jsonplaceholder.typicode.com/posts/:id - Afficher titre et contenu
- Fil d'Ariane (Breadcrumbs) :
- Accueil / Utilisateur X / Post Y
- Lien retour vers le profil de l'auteur
Fonctionnalités Avancées
-
Recherche en Temps Réel
javascriptconst [search, setSearch] = useState(''); const filteredUsers = users.filter(user => user.name.toLowerCase().includes(search.toLowerCase()) ); -
Fetch Parallèle (sur la page utilisateur)
javascriptconst [userRes, postsRes] = await Promise.all([ fetch(`/users/${userId}`), fetch(`/posts?userId=${userId}`) ]); -
Compteurs
- Nombre total d'utilisateurs
- Nombre de posts par utilisateur
Points Clés
- useState pour
search,users,loading - useEffect pour fetch au mount et quand userId change
.filter()pour la recherchePromise.all()pour performance- Gérer les cas où l'utilisateur n'existe pas (404)
Attention
JSONPlaceholder limite les résultats. Si un userId n'existe pas (ex: 999), l'API peut retourner un objet vide {} au lieu d'une erreur 404.
Vérifiez toujours if (data.id) après le fetch !
const data = await response.json();
if (!data.id) {
setNotFound(true);
return;
}
Exercice 4 : Application Météo
Difficulté : Difficile
Durée estimée : 90+ minutes
Objectif : Créer une application météo avec routing et recherche.
Consignes
Créez une application météo avec les pages suivantes :
Page : Recherche (/)
- Input pour chercher une ville
- Bouton "Rechercher"
- Au submit : naviguer vers
/weather/:cityName - Afficher les dernières recherches (max 5)
Page : Météo d'une Ville (/weather/:city)
- Fetch la météo depuis une API (voir APIs suggérées)
- Afficher :
- Nom de la ville
- Température actuelle
- Description (ensoleillé, nuageux, etc.)
- Icône météo
- Humidité, vent, etc.
- Bouton "Nouvelle recherche" → retour vers
/ - Gérer les villes inexistantes (404)
APIs Meteo Suggerees
Option 1 : wttr.in (Recommande - Pas d'API key)
// Pas d'inscription necessaire - pret a utiliser !
const city = 'Paris';
fetch(`https://wttr.in/${city}?format=j1`)
.then(res => res.json())
.then(data => {
console.log(data.current_condition[0].temp_C); // Temperature
console.log(data.current_condition[0].weatherDesc[0].value); // Description
console.log(data.current_condition[0].humidity); // Humidite
console.log(data.current_condition[0].windspeedKmph); // Vent
});
Option 2 (Bonus) : OpenWeatherMap (Inscription requise)
// Inscription : https://openweathermap.org/api
// API Key gratuite mais necessite un compte
const API_KEY = 'YOUR_API_KEY';
const city = 'Paris';
fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}&units=metric&lang=fr`)
.then(res => res.json())
.then(data => {
console.log(data.name); // "Paris"
console.log(data.main.temp); // 15.5
console.log(data.weather[0].description); // "ciel degage"
});
Conseil
Commencez avec wttr.in pour eviter la friction de creer un compte API. Si vous voulez aller plus loin, OpenWeatherMap offre des donnees plus detaillees (icones meteo, previsions, etc.).
Fonctionnalités Avancées
-
Historique de Recherche
javascriptconst [history, setHistory] = useState([]); // Ajouter à l'historique useEffect(() => { if (city && !history.includes(city)) { setHistory(prev => [city, ...prev].slice(0, 5)); } }, [city]); -
Persistence avec localStorage
javascript// Sauvegarder l'historique useEffect(() => { localStorage.setItem('searchHistory', JSON.stringify(history)); }, [history]); // Charger au mount useEffect(() => { const saved = localStorage.getItem('searchHistory'); if (saved) { setHistory(JSON.parse(saved)); } }, []); -
Loading State Visuel
- Afficher un spinner ou un skeleton pendant le fetch
- Désactiver le bouton de recherche pendant le chargement
-
Gestion d'Erreurs
- Ville non trouvée → Message clair
- Erreur réseau → Message de retry
- Afficher un composant
<ErrorPage />
Points Clés
- Navigation programmatique avec
useNavigate() - useEffect avec
[city]pour re-fetch si on change de ville - Gérer les erreurs API (ville invalide)
- AbortController pour cleanup
- Search params pour des filtres (unités : °C / °F)
Bonus
Améliorations possibles :
- Prévisions sur 5 jours
- Géolocalisation pour la ville actuelle
- Toggle °C / °F
- Mode sombre
- Animations de transition
- Autocomplete pour les villes
Exercice 5 : Dashboard avec Tabs
Difficulté : Moyenne
Durée estimée : 45 minutes
Objectif : Créer un dashboard avec navigation par onglets via l'URL.
Consignes
Créez un dashboard avec 3 onglets gérés par l'URL :
Onglets
/dashboard(ou/dashboard/overview) → Vue d'ensemble/dashboard/users→ Liste d'utilisateurs/dashboard/posts→ Liste de posts
Fonctionnalités
- Navigation par onglets (Tabs)
- L'onglet actif est mis en surbrillance
- Chaque onglet fetch ses propres données
- URL reflète l'onglet actif (bookmarkable)
Structure
<Routes>
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<Overview />} />
<Route path="users" element={<UserTab />} />
<Route path="posts" element={<PostTab />} />
</Route>
</Routes>
// Dashboard.jsx
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<nav className="tabs">
<NavLink to="/dashboard">Overview</NavLink>
<NavLink to="/dashboard/users">Users</NavLink>
<NavLink to="/dashboard/posts">Posts</NavLink>
</nav>
<div className="tab-content">
<Outlet />
</div>
</div>
);
}
CSS pour les Tabs
.tabs {
display: flex;
gap: 1rem;
border-bottom: 2px solid #ddd;
}
.tabs a {
padding: 0.5rem 1rem;
text-decoration: none;
color: #666;
}
.tabs a.active {
color: #0066cc;
border-bottom: 2px solid #0066cc;
margin-bottom: -2px;
}
APIs Publiques pour Pratiquer
APIs Gratuites
JSONPlaceholder (pas d'auth)
Users: https://jsonplaceholder.typicode.com/users
Posts: https://jsonplaceholder.typicode.com/posts
Photos: https://jsonplaceholder.typicode.com/photos
Todos: https://jsonplaceholder.typicode.com/todos
Random User API
https://randomuser.me/api/?results=10
REST Countries
https://restcountries.com/v3.1/all
https://restcountries.com/v3.1/name/france
Cat Facts API
https://catfact.ninja/fact
https://catfact.ninja/facts?limit=10
PokéAPI
https://pokeapi.co/api/v2/pokemon?limit=20
https://pokeapi.co/api/v2/pokemon/pikachu
Checklist de Validation
Avant de considérer un exercice terminé, vérifiez :
- [ ] Routing : Les URLs correspondent aux pages affichées
- [ ] Navigation : Les liens
<Link>fonctionnent correctement - [ ] useEffect : Fetch au bon moment (mount, changement de params)
- [ ] Loading : État de chargement visible pendant le fetch
- [ ] Erreurs : Messages d'erreur clairs si problème
- [ ] useParams : Paramètres d'URL correctement extraits
- [ ] Cleanup : Pas de warnings "Can't perform state update on unmounted component"
- [ ] Dépendances : Toutes les dépendances dans le tableau de
useEffect - [ ] 404 : Page "Non trouvée" pour les URLs invalides
- [ ] UX : Navigation intuitive, breadcrumbs si pertinent
Récapitulatif
Ce que vous avez pratiqué
Exercice 1 : Fetch de données avec useEffect, gestion loading/error, bouton de rafraîchissement
Exercice 2 : Application multi-pages avec React Router, routes statiques et dynamiques, fetch par paramètre d'URL
Exercice 3 : Annuaire avec recherche en temps réel, fetch parallèle avec Promise.all, navigation entre pages liées
Exercice 4 : Application météo complète avec navigation programmatique, localStorage, gestion d'erreurs avancée
Exercice 5 : Dashboard avec routes imbriquées (nested routes), Outlet, NavLink avec style actif
React Docs - useEffect
La documentation officielle sur useEffect
DocumentationReact Router Tutorial
Tutorial officiel de React Router v6
DocumentationMDN - Fetch API
Documentation complète de l'API Fetch
DocumentationJSONPlaceholder
API gratuite pour tester vos fetch
Bon courage ! N'hésitez pas à demander de l'aide si vous bloquez.