14

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.

Container Logic & Fetch Props Presentational

🛡️ 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.

Error Boundary class component
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;
    }
}
react-error-boundary (recommandé)
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 withAuth — Protection de route
// 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 withLogger — Debugging
// 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

Pattern Compound Components (ex: <Select>)
// 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
🚀 Pour aller plus loin : Explorez React Server Components (RSC) dans Next.js 14+ — une nouvelle architecture où certains composants s'exécutent exclusivement côté serveur, éliminant leur poids côté client.