Contactez-nous

Création et utilisation de portails (`ReactDOM.createPortal`)

Guide pratique pour implémenter des Portails en React : utilisation de ReactDOM.createPortal, préparation du noeud DOM cible et exemples concrets.

L'API `ReactDOM.createPortal` : La porte de sortie

La clé pour créer des portails en React réside dans l'utilisation de la fonction createPortal fournie par la bibliothèque react-dom. C'est cette fonction qui permet de dire à React : "Prends ces éléments enfants que je veux rendre, et au lieu de les ajouter ici dans le DOM, attache-les à cet autre endroit spécifié".

La signature de l'API est simple :

ReactDOM.createPortal(child, containerNode)
  • child : Le contenu React que vous souhaitez rendre via le portail. Cela peut être n'importe quel élément React valide : JSX, un composant, une chaîne, un nombre, un fragment, etc.
  • containerNode : Une référence directe à un noeud DOM existant dans la page. C'est l'endroit où le child sera physiquement ajouté dans l'arbre DOM.

Le point crucial est que le containerNode doit déjà exister dans le DOM lorsque le composant qui appelle createPortal est monté et rendu. Il ne peut pas être créé dynamiquement par React au même moment.

Préparer le conteneur DOM cible

Puisque le noeud DOM conteneur doit exister, la pratique la plus courante est de le préparer à l'avance dans votre fichier HTML principal (souvent public/index.html dans les projets Create React App ou Vite).

Vous pouvez simplement ajouter un élément

vide avec un ID unique, généralement juste avant la balise de fermeture , et en dehors de l'élément racine où votre application React principale est montée (souvent
).



  
    
    Mon App React
  
  
    
    
{ /* Racine de l'application React */} { /* Conteneur dédié pour les portails */}

Avoir ce conteneur dédié (ici portal-container) facilite la récupération de la référence DOM depuis vos composants React. Alternativement, vous pouvez utiliser directement document.body comme conteneur, ce qui est aussi très courant, surtout pour les modales plein écran.

Implémenter un composant utilisant `createPortal`

Une fois le conteneur DOM prêt, vous pouvez créer un composant React qui utilise createPortal. Voici un composant fonctionnel simple qui rend ses enfants dans le noeud #portal-container :

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

// Récupère la référence au conteneur du portail
const portalRoot = document.getElementById('portal-container');

function PortalWrapper({ children }) {
  // Utiliser un état pour s'assurer que portalRoot est disponible côté client
  // (document n'existe pas lors du rendu initial côté serveur si SSR)
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  // Ne tente de créer le portail que si le client est prêt et portalRoot existe
  if (!isClient || !portalRoot) {
    return null; // Ou un fallback si nécessaire pendant SSR
  }

  // Utilisation de createPortal pour rendre les enfants dans portalRoot
  return ReactDOM.createPortal(
    children,
    portalRoot
  );
}

export default PortalWrapper;

// --- Utilisation --- 
// function App() {
//   return (
//     
//

Contenu dans #root

// //

Ce titre est rendu dans #portal-container !

//
//
// ); // }

Ce composant `PortalWrapper` prend des `children` et utilise `ReactDOM.createPortal` pour les rendre dans le noeud DOM `portalRoot`. La vérification `isClient` et `portalRoot` assure que cela ne se produit que côté client et que le noeud cible existe.

Exemple plus complet : Composant `Modal`

Appliquons cela à un composant `Modal` plus réaliste. Souvent, une modale crée son propre conteneur DOM temporaire pour plus d'encapsulation, mais le principe du portail reste le même.

import React, { useEffect, useMemo } from 'react';
import ReactDOM from 'react-dom';

const modalRoot = document.getElementById('portal-container') || document.body; // Utilise le conteneur ou le body

function Modal({ children, isOpen, onClose }) {
  // Créer un élément div pour servir de conteneur spécifique à CETTE instance de modale
  // useMemo assure que l'élément n'est créé qu'une fois par instance
  const element = useMemo(() => document.createElement('div'), []);

  useEffect(() => {
    if (isOpen) {
      // Ajouter l'élément au DOM du portail lorsque la modale est ouverte
      modalRoot.appendChild(element);
      // Optionnel : Ajouter une classe au body pour empêcher le scroll derrière
      document.body.classList.add('modal-open');
    } else {
       document.body.classList.remove('modal-open');
    }

    // Fonction de nettoyage
    return () => {
      if (modalRoot.contains(element)) {
         modalRoot.removeChild(element);
      }
      document.body.classList.remove('modal-open');
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [isOpen]); // Dépend de l'état d'ouverture

  // Si la modale n'est pas ouverte, ne rien rendre
  if (!isOpen) {
    return null;
  }

  // Rendre le contenu de la modale dans l'élément créé via le portail
  return ReactDOM.createPortal(
    
{ /* Fond cliquable pour fermer */}
e.stopPropagation()}> { /* Empêche la fermeture si on clique sur le contenu */} {children}
, element // Rendu dans l'élément div temporaire attaché à modalRoot ); } export default Modal; // Utilisation: // function MonApp() { // const [modalOuverte, setModalOuverte] = useState(false); // return ( //
// // setModalOuverte(false)}> // Contenu de la modale... // //
// ); // }

Cet exemple `Modal` montre :

  • La création d'un élément DOM (`element`) pour contenir le rendu de cette instance de modale.
  • L'utilisation de `useEffect` pour ajouter/retirer cet `element` du `modalRoot` (ou `body`) lorsque la prop `isOpen` change.
  • Le rendu conditionnel pour n'appeler `createPortal` que lorsque `isOpen` est vrai.
  • L'appel à `createPortal` qui cible l'élément `element` créé dynamiquement.

Intégration transparente dans l'arbre react

Le point clé est que, du point de vue du composant parent (`MonApp` dans l'exemple), le composant `Modal` ou `PortalWrapper` s'utilise comme n'importe quel autre composant React. Le parent n'a pas besoin de savoir que le rendu DOM de son enfant se fait ailleurs.

Toutes les interactions logiques de React (passage de props comme `children` ou `onClose`, accès au contexte, propagation des événements) fonctionnent comme si le composant était rendu normalement dans la hiérarchie React. Seul l'emplacement final dans le DOM est modifié par `createPortal`.

C'est cette combinaison de flexibilité DOM et de cohérence logique React qui fait des portails un outil puissant et essentiel pour certains patterns d'interface utilisateur avancés.