Aller au contenu

React : données et routage

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.

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 :

Fenêtre de terminal
npm install @tanstack/react-query
import {
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.

É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).

Fenêtre de terminal
npm install react-router

src/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.

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.

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.