React est une bibliothèque JavaScript pour construire des interfaces utilisateur.
Elle permet de composer l’UI à partir de petits composants réutilisables, avec un rendu efficace grâce au Virtual DOM.
On l’utilise surtout pour des SPA (Single Page Applications) ou des interfaces riches consommant des APIs.
Questions d’entretien React
Une sélection de questions/réponses React pour préparer tes entretiens front. Utilise les onglets pour filtrer par niveau.
JSX est une extension de syntaxe pour JavaScript qui permet d’écrire du « HTML dans le JavaScript ».
En réalité, JSX est compilé vers des appels `React.createElement`.
Exemple :
function Hello() {
return <h1>Bonjour React !</h1>;
}
// compilé en quelque chose proche de :
React.createElement("h1", null, "Bonjour React !");
Historiquement, on utilisait les classes pour gérer l’état et le cycle de vie.
Depuis l’arrivée des Hooks, on écrit quasiment tout en composants fonctionnels :
• plus simples,
• mieux typés avec TypeScript,
• plus faciles à tester.
Les composants classe sont encore présents dans du legacy mais ne sont plus recommandés pour du nouveau code.
• props : données passées au composant depuis son parent. Elles sont en lecture seule pour le composant.
• state : données internes au composant, qui peuvent évoluer via `setState` ou `useState`.
Règle clé : un composant ne doit jamais modifier directement ses props, mais peut gérer son propre state.
Les `key` aident React à identifier chaque élément d’une liste pour optimiser le rendu.
Sans key stable, React aura plus de mal à savoir quel élément a été ajouté/supprimé et peut réutiliser le mauvais DOM.
Exemple :
{items.map(item => (
<li key={item.id}>{item.label}</li>
))}
On évite d’utiliser l’index du tableau comme key lorsque la liste peut changer (insertions/suppressions).
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(c => c + 1); // on utilise la version fonctionnelle
};
return (
<button onClick={handleClick}>
Tu as cliqué {count} fois
</button>
);
}
C’est le fait de remonter un state dans un composant parent afin de le partager entre plusieurs enfants.
Au lieu que chaque enfant ait son propre state et ne puisse pas se synchroniser, on place le state plus haut, et on passe :
• la valeur via les props,
• des callbacks pour le mettre à jour.
Cela permet d’avoir une source de vérité unique.
Un composant contrôlé est un input dont la valeur est pilotée par le state React.
Le DOM ne garde pas sa propre valeur, c’est React qui contrôle tout.
Exemple :
function LoginForm() {
const [email, setEmail] = useState("");
return (
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
);
}
On utilise généralement une librairie comme React Router.
Elle permet de définir des routes et d’afficher un composant différent selon l’URL, sans recharger la page complète.
useEffect permet d’exécuter un effet après le rendu : appel API, abonnement, manipulation de titre de page, etc.
• Si tu ne passes pas de tableau : l’effet s’exécute après chaque rendu.
• Tableau vide `[]` : l’effet s’exécute une seule fois (après le premier rendu).
• Avec des dépendances `[value]` : l’effet s’exécute lorsqu’une des dépendances change.
Exemple :
useEffect(() => {
document.title = `Compteur : ${count}`;
}, [count]); // se rejoue uniquement quand count change
• useMemo mémorise le résultat d’un calcul coûteux.
• useCallback mémorise une fonction (sa référence) pour éviter des rerenders inutiles plus bas.
Exemple :
const expensive = useMemo(() => doHeavyWork(data), [data]);
const handleClick = useCallback(() => {
console.log(expensive);
}, [expensive]);
Les deux doivent être utilisés avec parcimonie : inutile de les mettre partout, seulement là où il y a un vrai problème de performance.
Context permet de partager des données (thème, utilisateur, localisation…) sans prop drilling.
Exemple simplifié pour un thème :
const ThemeContext = createContext("dark");
export function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
const theme = useContext(ThemeContext);
return <button className={theme}>Bouton</button>;
}
React se base sur l’immuabilité pour détecter les changements.
Si tu modifies directement un objet ou un tableau (par exemple avec `array.push`), la référence ne change pas toujours de manière visible pour React.
On préfère créer une nouvelle référence :
// Mauvais
state.items.push(newItem);
setState(state);
// Bon
setItems(prev => [...prev, newItem]);
import { useEffect, useState } from "react";
export function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function run() {
setLoading(true);
try {
const res = await fetch(url);
if (!res.ok) throw new Error("Erreur HTTP");
const json = await res.json();
if (!cancelled) setData(json);
} catch (e) {
if (!cancelled) setError(e);
} finally {
if (!cancelled) setLoading(false);
}
}
run();
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
Ce hook peut ensuite être réutilisé dans plusieurs composants.
On crée un composant de classe spécial qui implémente `componentDidCatch` et `getDerivedStateFromError`.
Même si on n’utilise plus beaucoup les classes, les Error Boundaries sont encore basés dessus.
Exemple minimal :
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
// log vers Sentry par exemple
}
render() {
if (this.state.hasError) {
return <p>Une erreur est survenue.</p>;
}
return this.props.children;
}
}
Quelques techniques :
• Utiliser `React.memo` sur les éléments de liste pour éviter des rerenders inutiles.
• Utiliser une lib de virtualisation (React Window, React Virtualized) pour n’afficher qu’une partie de la liste en même temps.
• Bien choisir les `key` pour que React puisse réutiliser le DOM.
Pour éviter de réinventer cache, retry, refetch, etc., on peut utiliser des libs comme React Query.
Elles sortent l’état serveur du state local, gèrent le cache, les erreurs, la synchronisation onglet / focus, etc.
Exemple très simple avec React Query :
const { data, isLoading, error } = useQuery({
queryKey: ["todos"],
queryFn: () => fetch("/api/todos").then(r => r.json()),
});
Avec React Router v6 :
// Définition de route
<Route path="/users/:id" element={<UserPage />} />
// Dans UserPage
import { useParams } from "react-router-dom";
function UserPage() {
const { id } = useParams(); // id dans l’URL
// fetch user ici
}
Le Virtual DOM est une représentation en mémoire de l’arbre d’UI.
Lorsqu’un state change, React :
1. recalcul l’arbre virtuel,
2. le compare à l’arbre précédent (diffing),
3. applique au DOM réel uniquement les changements minimaux.
La reconciliation est l’algorithme qui fait ce diff, en utilisant les `key` pour les listes, ce qui rend le rendu performant même sur de grosses interfaces.
Plusieurs leviers :
• Découper l’UI en petits composants.
• Utiliser `React.memo` sur des composants purement fonctionnels.
• Utiliser `useMemo` et `useCallback` sur des calculs ou callbacks coûteux.
• Éviter de recréer des objets / tableaux à chaque rendu (ex. éviter `onClick={() => ...}` dans certains cas critiques, ou memoïser les handlers).
Mais attention : optimiser seulement après avoir mesuré avec le Profiler, pas à l’aveugle.
Le code splitting permet de découper ton bundle en plusieurs morceaux (chunks) chargés à la demande.
Avec React.lazy et Suspense, tu peux charger un composant uniquement quand il est nécessaire.
Exemple :
const AdminPage = React.lazy(
() => import("./pages/AdminPage")
);
function App() {
return (
<Suspense fallback={<p>Chargement...</p>}>
<Routes>
<Route path="/admin" element={<AdminPage />} />
</Routes>
</Suspense>
);
}
• CSR (Client-Side Rendering) : rendu 100 % dans le navigateur (classique create-react-app).
• SSR (Server-Side Rendering) : le HTML initial est rendu côté serveur pour chaque requête (Next.js `getServerSideProps`).
• SSG (Static Site Generation) : les pages sont générées à build time et servies en statique.
• ISR (Incremental Static Regeneration) : pages statiques mais régénérées périodiquement.
Next.js combine ces modes pour optimiser SEO, temps de chargement et cache.
• Côté backend : API .NET (Minimal APIs ou contrôleurs), authentification JWT ou cookies, validations, logs, etc.
• Côté frontend : React (Vite ou CRA), routing (React Router), state local par features, éventuellement état global (Redux / Zustand / Query).
• Dossier par feature : `features/users`, `features/orders` avec composants, hooks, services.
• Contrats typés (TypeScript) alignés avec les DTO de l’API (générés ou partagés via OpenAPI).
• Utiliser les bonnes balises sémantiques (`<button>`, `<nav>`, `<main>`, etc.).
• Ajouter les attributs ARIA uniquement lorsque nécessaire (`aria-expanded`, `aria-label`…).
• Gérer le focus correctement (modales, menus, clavier).
• Tester avec un lecteur d’écran et les outils Lighthouse / axe.
Les libs de composants sérieuses (MUI, Radix UI…) intègrent déjà beaucoup de bonnes pratiques a11y.
On utilise :
• Jest comme runner,
• React Testing Library pour tester l’UI du point de vue de l’utilisateur.
Exemple :
import { render, screen, fireEvent } from "@testing-library/react";
import { Counter } from "./Counter";
test("incrémente le compteur", () => {
render(<Counter />);
const btn = screen.getByRole("button");
fireEvent.click(btn);
expect(btn).toHaveTextContent("1");
});
React échappe par défaut le contenu inséré dans le JSX, ce qui limite le XSS.
Le risque principal vient de `dangerouslySetInnerHTML` ou de libs qui insèrent du HTML non nettoyé.
Bonnes pratiques :
• ne jamais injecter du HTML brut venant de l’utilisateur sans le sanitizer,
• valider/filtrer côté backend également,
• éviter les constructions dynamiques d’HTML quand ce n’est pas nécessaire.
• Pour un petit périmètre (thème, user courant) : Context + hooks.
• Pour un état global complexe (filtres, panier, règles métier) : un store dédié (Redux Toolkit, Zustand, Jotai…).
• Pour l’état serveur (données venant de l’API) : React Query / SWR plutôt qu’un store généraliste.
L’idée est de séparer :
• état local UI (useState),
• état serveur cache (Query),
• état global métier (Redux/Zustand).
• Utiliser le Profiler de React DevTools pour voir quels composants rerender trop.
• Observer la taille des bundles (Webpack Bundle Analyzer, outil Vite…).
• Mesurer le TTFB / LCP / CLS via Lighthouse et les DevTools.
• Regarder les timelines de rendu dans le navigateur.
Une fois les hotspots identifiés, on applique les optimisations (memo, virtualisation, lazy loading).