React : données et routage
Charger des données avec fetch
Section intitulée « Charger des données avec fetch »import { useEffect, useState } from 'react';
type Client = { id: number; nom: string };
function useClients() { const [clients, setClients] = useState<Client[]>([]); const [chargement, setChargement] = useState(true); const [erreur, setErreur] = useState<string | null>(null);
useEffect(() => { const annulation = new AbortController(); fetch('/api/clients', { signal: annulation.signal }) .then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); }) .then((donnees: Client[]) => setClients(donnees)) .catch((e: Error) => { if (e.name !== 'AbortError') setErreur(e.message); }) .finally(() => setChargement(false)); return () => annulation.abort(); }, []);
return { clients, chargement, erreur };}
export function Liste() { const { clients, chargement, erreur } = useClients(); if (chargement) return <p>Chargement…</p>; if (erreur) return <p role="alert">Erreur : {erreur}</p>; return ( <ul> {clients.map((c) => ( <li key={c.id}>{c.nom}</li> ))} </ul> );}AbortController annule la requête au démontage du composant ou au changement de dépendance : sans cela, une réponse tardive peut écraser des données plus récentes.
TanStack Query : la solution recommandée
Section intitulée « TanStack Query : la solution recommandée »Pour tout autre cas que l’exemple minimal, une bibliothèque de données gère le cache, les rechargements, les erreurs et les mutations :
npm install @tanstack/react-queryimport { QueryClient, QueryClientProvider, useQuery, useMutation, useQueryClient,} from '@tanstack/react-query';
const client = new QueryClient();
export function Racine() { return ( <QueryClientProvider client={client}> <Clients /> </QueryClientProvider> );}
function Clients() { const requete = useQuery({ queryKey: ['clients'], queryFn: async () => { const r = await fetch('/api/clients'); if (!r.ok) throw new Error(`HTTP ${r.status}`); return (await r.json()) as { id: number; nom: string }[]; }, });
const qc = useQueryClient(); const ajout = useMutation({ mutationFn: (nom: string) => fetch('/api/clients', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nom }), }), onSuccess: () => qc.invalidateQueries({ queryKey: ['clients'] }), });
if (requete.isPending) return <p>Chargement…</p>; if (requete.isError) return <p role="alert">{requete.error.message}</p>; return ( <> <ul> {requete.data.map((c) => ( <li key={c.id}>{c.nom}</li> ))} </ul> <button onClick={() => ajout.mutate('Nouveau client')}>Ajouter</button> </> );}L’API appelée peut être construite avec Express.
Appeler l’API pendant le développement
Section intitulée « Appeler l’API pendant le développement »Éviter les problèmes CORS en faisant relayer les appels par Vite :
vite.config.ts :
export default defineConfig({ plugins: [react()], server: { proxy: { '/api': { target: 'http://127.0.0.1:3000', changeOrigin: true } }, },});En production, le reverse proxy fait le même travail (voir Performance et déploiement).
Routage avec React Router
Section intitulée « Routage avec React Router »npm install react-routersrc/main.tsx :
import { createBrowserRouter, RouterProvider } from 'react-router';import App from './App';import Accueil from './pages/Accueil';import Client from './pages/Client';import Introuvable from './pages/Introuvable';
const routeur = createBrowserRouter([ { path: '/', element: <App />, children: [ { index: true, element: <Accueil /> }, { path: 'clients/:id', element: <Client /> }, { path: '*', element: <Introuvable /> }, ], },]);
createRoot(document.getElementById('root')!).render( <RouterProvider router={routeur} />,);App.tsx (mise en page commune) :
import { Link, NavLink, Outlet } from 'react-router';
export default function App() { return ( <> <nav> <NavLink to="/" end> Accueil </NavLink> <Link to="/clients/1">Client 1</Link> </nav> <Outlet /> </> );}import { useNavigate, useParams, useSearchParams } from 'react-router';
export default function Client() { const { id } = useParams(); const [recherche, setRecherche] = useSearchParams(); const navigate = useNavigate(); return ( <> <h1>Client {id}</h1> <button onClick={() => navigate('/')}>Retour</button> <button onClick={() => setRecherche({ onglet: 'commandes' })}> Onglet commandes </button> </> );}| Élément | Rôle |
|---|---|
Link, NavLink |
Liens sans rechargement (NavLink : style du lien actif) |
Outlet |
Emplacement des routes enfants |
useParams |
Paramètres de l’URL (:id) |
useSearchParams |
Chaîne de requête |
useNavigate |
Navigation programmatique |
Les routes de React Router sont côté navigateur : le serveur web doit renvoyer index.html pour toute URL inconnue (voir Performance et déploiement). React Router peut aussi fonctionner en mode « framework » avec rendu serveur ; ce guide traite le mode simple.
Protéger une route
Section intitulée « Protéger une route »function RoutePrivee({ children }: { children: React.ReactNode }) { const { utilisateur } = useAuth(); return utilisateur ? children : <Navigate to="/connexion" replace />;}Une route protégée côté navigateur ne protège pas les données : l’API doit vérifier l’authentification et les droits de chaque requête.
Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
| Les données se chargent deux fois en développement | StrictMode : normal en développement, ajouter l’annulation |
CORS error |
Utiliser le proxy de Vite en développement, configurer le serveur de l’API en production |
Unexpected token '<' à la lecture du JSON |
L’API a renvoyé du HTML (404 ou page d’index) : vérifier l’URL et le proxy |
404 en rechargeant /clients/1 en production |
Le serveur doit renvoyer index.html pour les chemins inconnus |
| Les données ne se rafraîchissent pas | Clé de requête identique, cache de TanStack Query : invalidateQueries |
useNavigate() may be used only in the context of a <Router> |
Composant hors du RouterProvider |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune.