Retour à l'accueil

Pile modale React : Architecture pour l'IT

L'article décrit l'architecture d'une pile modale typée pour les applications React. Résout les problèmes de duplication DOM, prop drilling et dépendances via slice Redux, hooks de superposition et wrappers de source. Fournit des exemples de code et des stratégies de mise à l'échelle.

Pile de modales typée dans React pour UI complexe
Advertisement 728x90

Architecture de pile modale React pour apps évolutives

Dans les applications React complexes, les modales deviennent vite un casse-tête. Une modale simple avec useState fonctionne isolément, mais à mesure que le projet grandit, vous finissez avec des composants dupliqués, du prop drilling et des dépendances d'état emmêlées. La solution ? Une pile de superpositions typée qui sépare la logique par source de déclenchement.

Prenons un tableau de bord d'administration de marketplace typique avec un tableau de produits. Chaque ligne nécessite une modale pour ajouter des identifiants (IMEI, SGTIN), avec tableaux de commandes, validation, imports Excel et un assistant multi-étapes.

Problèmes du rendu local

Rendre un IdentificationModal dans chaque ligne de tableau crée des milliers d'éléments DOM cachés. Le composant est bourré de tableaux, d'entrées masquées, de hooks RTK Query et d'un stepper — la consommation mémoire explose.

Google AdInline article slot

Voici un mauvais exemple :

const ProductRow = ({ product }) => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <tr>
      <td>{product.name}</td>
      <td>
        <Button onClick={() => setIsOpen(true)}>Ajouter identifiants</Button>
        <IdentificationModal
          isOpen={isOpen}
          onClose={() => setIsOpen(false)}
          productId={product.id}
        />
      </td>
    </tr>
  );
};

Leçon clé : Les modales doivent être rendues une seule fois au niveau de la page.

Prop drilling et état global

Déplacer la modale vers le haut de l'arbre corrige le gonflement DOM, mais oblige à passer des callbacks à travers 4-5 couches de composants :

Google AdInline article slot
  • ProductsPageProductsTableTableBodyProductRowActionsMenu

Redux avec des dispatchs openIdent(productId) élimine les props, mais devient chaotique avec plusieurs contextes.

Multiples sources de données

La modale puise dans trois endroits avec des API différentes :

| Source de déclenchement | Commandes | Chargement données | Modèle Excel |

Google AdInline article slot

|--------------------------|-----------|--------------------|--------------|

| En-tête de tableau | Toutes les commandes | ordersApi.getIdentOrders() | ordersApi.getExcel() |

| Ligne de tableau | Une commande | Filtre par orderId | ordersApi.getExcel() |

| Panier | Commandes du panier | cartsApi.getCartIdentOrders(cartId) | cartsApi.getExcel() |

Entasser la logique dans la modale en fait un God Object plein de if, d'invalidation de cache et d'analytique. L'alternative — une modale UI pure avec props orders et onSave — échoue car la page ignore le contexte à l'avance.

Dépendances des modales

Ouvrir la modale d'identifiant masque le panneau d'actions groupées ; la fermer le restaure. Ajouter une modale de confirmation (« Modifications non sauvegardées ? ») oblige à jongler manuellement avec des flags :

openConfirmClose: (state) => {
  state.isConfirmOpen = true;
  state.isIdentOpen = false;
},
cancelConfirmClose: (state) => {
  state.isConfirmOpen = false;
  state.isIdentOpen = true;
},

Des structures de flags plats ne capturent ni l'ordre d'empilement ni les stratégies de rollback.

Architecture de solution : Pile typée

Ce système résout trois problèmes d'un coup :

  • Rendu unique via une pile Redux avec file d'attente de superpositions.
  • Séparation des contextes par wrappers de sources.
  • Gestion automatique des dépendances avec règles de pile.

Slice Redux pour la pile

export enum EOverlayName {
  BulkActions = 'bulkActions',
  IdentFromHeader = 'identFromHeader',
  IdentFromRow = 'identFromRow',
  IdentFromCart = 'identFromCart',
  EditProduct = 'editProduct',
  // ...
}

type OverlayItem<TData = unknown> = {
  name: EOverlayName;
  data: TData;
  priority?: number;
};

type OverlayStack = OverlayItem[];

La pile stocke des données typées et l'ordre. Les actions asynchrones poussent des superpositions avec payloads.

Hooks de superposition pour déclencheurs

const useOverlay = () => {
  const dispatch = useDispatch();

  const open = <TData>(name: EOverlayName, data: TData) => {
    dispatch(openOverlay({ name, data }));
  };

  const close = (name: EOverlayName) => {
    dispatch(closeOverlay(name));
  };

  return { open, close };
};

Bouton de ligne : open('identFromRow', { productId, orderId }). La modale lit directement dans la pile — fini le prop drilling.

Wrappers de sources pour contextes

Chaque source exporte un adaptateur :

// identFromRow.adapter.ts
export const useIdentFromRowAdapter = () => {
  const { open } = useOverlay();

  const openIdent = (productId: string, orderId: string) => {
    const orders = ordersApi.useGetIdentOrdersQuery();
    const filtered = orders.data?.filter(o => o.id === orderId);
    open('identFromRow', { orders: filtered, productId });
  };

  return { openIdent };
};

La modale reçoit des orders préparées et onSave sans connaître la source.

Stratégies d'ouverture/fermeture

La pile impose des règles :

  • Remplacement : Ouvrir ident ferme bulkActions.
  • Pause : Fermer reprend la superposition précédente.
  • Effacement : Confirmation réinitialise la pile.
const openOverlay = createAsyncThunk(
  'overlay/open',
  async ({ name, data }, { getState }) => {
    const stack = selectStack(getState());
    // Logique de remplacement/priorité
    return { name, data };
  }
);

Avantages clés

  • La pile élimine prop drilling, duplication DOM et dépendances d'état avec une seule architecture.
  • Les wrappers de sources isolent la logique API ; les modales restent UI pures.
  • Le typage OverlayItem<TData> garantit la sécurité des données.
  • Les stratégies asynchrones automatisent l'ordre d'ouverture/fermeture.
  • Scalable à des dizaines de modales sans alourdir le reducer.

— Editorial Team

Advertisement 728x90

Lire ensuite