Patterns Avancés
Error Boundaries, HOC, Compound Components & Architecture
Le concept : Avec l'expérience, la communauté React a dégagé des "Design Patterns" (modèles de conception) pour résoudre des problèmes d'architecture récurrents.
Le pattern le plus célèbre est la séparation Container / Presentational. Il consiste à faire un composant 'Intelligent' (le Container) qui gère toute la logique, le Fetch, le State... et qui passe ensuite ces données toutes prêtes à un composant 'Idiot' (le Presentational) qui ne sait faire qu'une chose : afficher de l'HTML et être joli.
🛡️ Error Boundaries — Gérer les erreurs en production
💡 Pourquoi les Error Boundaries ?
Sans Error Boundary, une erreur JS non capturée dans un composant plante toute votre application avec un écran blanc. Une Error Boundary est un composant qui attrape les erreurs dans son sous-arbre et affiche une UI de repli plutôt que de crasher.
C'est le seul cas où les Class Components restent nécessaires (pas encore d'équivalent Hook pour componentDidCatch). En pratique, utilisez la bibliothèque react-error-boundary.
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, info) { // Logger l'erreur (Sentry, Datadog...) logError(error, info.componentStack); } render() { if (this.state.hasError) { return this.props.fallback ?? <h2>Oups 😢</h2>; } return this.props.children; } }
import { ErrorBoundary } from 'react-error-boundary'; function ErrorFallback({ error, resetErrorBoundary }) { return ( <div role="alert"> <p>Une erreur s'est produite :</p> <pre>{error.message}</pre> <button onClick={resetErrorBoundary}> Réessayer </button> </div> ); } // Utilisation : envelopper des sections à risque <ErrorBoundary FallbackComponent={ErrorFallback} onError={logError} > <DataWidget /> </ErrorBoundary>
🔄 Higher-Order Component (HOC)
💡 Qu'est-ce qu'un HOC ?
Un HOC est une fonction qui prend un composant en argument et retourne un nouveau composant enrichi. C'est le pattern de composition le plus ancien de React (avant les hooks). En 2025, les Custom Hooks remplacent la plupart des HOC, mais ils restent utiles pour certains cas (injection de props, wrapping de librairies tierces).
// HOC : fonction qui prend un composant et en retourne un autre function withAuth(WrappedComponent) { return function AuthenticatedComponent(props) { const { user } = useUser(); if (!user) { return <Redirect to="/login" />; } return <WrappedComponent {...props} user={user} />; }; } // Utilisation const ProtectedDashboard = withAuth(Dashboard); // ProtectedDashboard redirige si non connecté
// HOC de logging : logge chaque rendu function withLogger(WrappedComponent) { return function LoggedComponent(props) { useEffect(() => { console.log( `[Render] ${WrappedComponent.name}`, props ); }); return <WrappedComponent {...props} />; }; } // Alternative moderne : custom hook useLogger function useRenderCount(name) { const count = useRef(0); useEffect(() => { console.log(name, ++count.current); }); }
🧩 Compound Components — API composable
// Un composant parent gère l'état partagé // Les enfants s'y connectent via Context implicite const TabsContext = createContext(null); function Tabs({ children, defaultTab }) { const [active, setActive] = useState(defaultTab); return <TabsContext.Provider value={{ active, setActive }}>{children}</TabsContext.Provider>; } Tabs.Tab = function({ label, id }) { const { active, setActive } = useContext(TabsContext); return <button onClick={() => setActive(id)} className={active === id ? 'active' : ''}>{label}</button>; }; Tabs.Panel = function({ id, children }) { const { active } = useContext(TabsContext); return active === id ? <div>{children}</div> : null; }; // Utilisation : API fluide et lisible <Tabs defaultTab="infos"> <Tabs.Tab id="infos" label="Infos" /> <Tabs.Tab id="settings" label="Réglages" /> <Tabs.Panel id="infos"> Contenu des infos...</Tabs.Panel> <Tabs.Panel id="settings"> Formulaire...</Tabs.Panel> </Tabs>
🏗️ Bonnes pratiques d'architecture
📐 Principes fondamentaux
- Single Responsibility : un composant = une responsabilité
- Composition over Inheritance : composez des petits composants
- Colocate State : state aussi proche que possible de là où il est utilisé
- Inversion of Control : les parents contrôlent le comportement via props/callbacks
- Préférez les Custom Hooks aux HOC : plus lisibles, plus testables
📚 Écosystème 2025
- 🌐 Next.js — React + SSR/SSG/App Router
- 🔀 React Router v7 — Navigation SPA
- 📡 TanStack Query — Fetching + cache serveur
- 🗂️ Zustand / Jotai — State global minimal
- 📝 React Hook Form — Formulaires performants
- 🧪 Vitest + Testing Library — Tests unitaires
- ⚡ Vite — Bundler / Dev server