Tout savoir sur le design pattern Container / Presentational
Tags: react, designpatterns, webdev, frontend
Published: 2023-04-19 16:53:36

Dans cet article, je vais vous présenter le **Design Pattern Container / Presentationnal** au travers d’un exemple. Nous verrons à quoi sert ce design pattern et quels sont ses avantages.

Vous pouvez retrouver le projet de démonstration à l’adresse suivante : 🔗 https://github.com/Delmotte-Vincent/container-presentational-pattern-demo

&nbsp;
# Container / Presentational Pattern

Le but d’un **design pattern** est de répondre à un problème fréquent de programmation. Le design **pattern container / presentational** répond à une problématique rencontré dans le développement frontend **React** en particulier. 

&nbsp;
## Le problème

Il est courant, lorsqu’on construit une interface utilisateur, de devoir faire des appels à une API afin de récupérer des données pour les afficher à l’écran. Il est également courant de se retrouver avec des composants **trop grands** et **illisibles** car les appels réseaux, la logique de données et l’affichage sont gérés au même endroit. 

Prenons un exemple simple. Vous voulez afficher une liste de Pokémon que vous récupérez d’une API.  

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2v3oqxt29gfy486gypf5.png)

Pour cela nous pouvons faire un composant **Pokemon** comme ci-dessous: 

```jsx
import { useState, useEffect } from 'react';

export default function Pokemon() {
  const [pokemons, setPokemons] = useState(null);

  useEffect(() => {
    const fetchPokemons = async () => {
      const pokemonsLinks = await fetch('https://pokeapi.co/api/v2/pokemon?limit=20')
        .then((response) => response.json())
        .then((response) => response.results);

      const pokemons = await Promise.all(
        pokemonsLinks.map((pokemon) => fetch(pokemon.url).then((response) => response.json()))
      );
      setPokemons(pokemons);
    };

    fetchPokemons().catch(console.error);
  }, []);

  if (!pokemons) return null;

  return (
    <div className="pokemon-list">
      {pokemons.map((pokemon, index) => (
        <div key={index} className="card-container">
          <img src={pokemon.sprites.front_default} alt={pokemon.name} />
          <div className="description-container">
            <div className="pokemon-description">
              <h1>{pokemon.name}</h1>
              <div className="types">
                {pokemon.types.map((type, index) => (
                  <div className="type-chip" key={index}>
                    {type.type.name}
                  </div>
                ))}
              </div>
            </div>
            <div className="stats">
              <p>hp : {pokemon.stats[0].base_stat}</p>
              <p>attack : {pokemon.stats[1].base_stat}</p>
              <p>defense : {pokemon.stats[2].base_stat}</p>
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}
```

Ce composant ne fait pas beaucoup de chose mais il est déjà composé de 50 lignes. Imaginez un composant qui gère beaucoup plus de données et dont la vue est plus complexe. On arrive vite à un fichier illisible.

&nbsp;
## La solution

Le design pattern **container / presentational**, comme son nom l’indique, propose de séparer les différentes opérations qu’un composant effectue. En effet, un composant peut effectuer deux actions : récupérer des données et afficher des données. 

En appliquant ce design pattern, on obtient le découpage suivant :

- Le **container** component est en charge de la **récupération / traitement des données**.
- Le **presentational** component est en charge de l’**affichage des données**.

Si on reprend l’exemple précédant en appliquant ce pattern on obtient l’architecture suivante :

- Un **container** component **PokemonListContainer** qui appelle l’api PokeAPI et se charge de mettre en forme les données.
- Un **presentational** component **PokemonList** qui récupère les données  différents des Pokémons et se charge de les afficher. Ici on ajoute un deuxième presentational component **PokemonCard** qui permet d’afficher chacun des Pokémons dans une card.

Voici l’architecture de dossier que j’ai choisi d’implémenter :


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s2rz14rblhzy411f9oru.png)

Voici l’implémentation des différents composants.

&nbsp;
### Container

```jsx
// PokemonListContainer.jsx

export default function PokemonListContainer() {
  const [pokemons, setPokemons] = useState(null);

  useEffect(() => {
    const fetchPokemons = async () => {
      const pokemonsLinks = await fetch('https://pokeapi.co/api/v2/pokemon?limit=20')
        .then((response) => response.json())
        .then((response) => response.results);

      const pokemons = await Promise.all(
        pokemonsLinks.map((pokemon) => fetch(pokemon.url).then((response) => response.json()))
      );
      setPokemons(pokemons);
    };

    fetchPokemons().catch(console.error);
  }, []);

  if (!pokemons) return null;

  return <PokemonList pokemons={pokemons} />;
}
```

&nbsp;
### Presentational

```jsx
// PokemonList.jsx

export default function PokemonList({ pokemons }) {
  return (
    <div className="pokemon-list">
      {pokemons.map((pokemon, index) => (
        <PokemonCard key={index} pokemon={pokemon} />
      ))}
    </div>
  );
}
```

```jsx
// PokemonCard.jsx

export default function PokemonCard({ pokemon }) {
  return (
    <div className="card-container">
      <img src={pokemon.sprites.front_default} alt={pokemon.name} />
      <div className="description-container">
        <div className="pokemon-description">
          <h1>{pokemon.name}</h1>
          <div className="types">
            {pokemon.types.map((type, index) => (
              <div className="type-chip" key={index}>
                {type.type.name}
              </div>
            ))}
          </div>
        </div>
        <div className="stats">
          <p>hp : {pokemon.stats[0].base_stat}</p>
          <p>attack : {pokemon.stats[1].base_stat}</p>
          <p>defense : {pokemon.stats[2].base_stat}</p>
        </div>
      </div>
    </div>
  );
}
```

En appliquant le **design pattern Container / Presentational** on obtient des fichiers plus **concis** et plus **lisibles**. De plus, on sait exactement ce qu’on va trouver dans chacun des fichiers car chacun à une fonction précise.

&nbsp;
# Avantages

Ce design pattern respect une règle importante de l’architecture logiciel qui est la **Separation Of Concern (SOC)**. Cette règle indique que dans une application, chaque composant logiciel doit avoir une fonction et une seule. Le but est de séparer tous les traitements de nature différente dans des composants séparés (ou modules) et de regrouper ceux qui font les mêmes traitements.

C’est exactement ce qu’on a fait ici. Nous avons le container qui gère la récupération et la transformation des données, tandis que les presentionals gèrent l’affichage des données. On peut voir ces deux modules représentés par les deux dossiers dans l’arborescence de fichier. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g9t53adfj4wzwmijr7qi.png)

&nbsp;
Le premier avantage d’appliquer ce design pattern est le **gain de lisibilité**. Les composants sont beaucoup plus **lisibles** car ils sont plus petits et tout le code qui est dans ce composant n’a qu’**un seul but**. On peut **anticiper** ce qu’on va trouver dans le code et il n’y a **pas de mauvaises surprises.**

&nbsp;
Le deuxième avantage est la **facilité de débuggage**. En effet, comme la logique est séparée de la vue, il est facile de converger **rapidement** vers le morceau de code d'où le **bug** provient. On simplifie le processus de débugge et ainsi on **gagne du temps**.

&nbsp;
Le troisième avantage est la **réutilisabilité**. En effet, comme la récupération des données, le traitement et l’affichage sont séparés, on obtient des composants qui sont facilement **réutilisable**. Plus le composant est découpé finement, moins il fait de choses, différentes, plus il sera facile de l’inclure ailleurs dans l’application.

&nbsp;
Le dernier avantage est la **testabilité**. Les presentational components se chargent de créer l’interface utilisateur en fonction des données d’entrées. Il sont donc **prévisibles** ce qui les rend **testable** très facilement. On peut faire une analogie entre le presentational component et la fonction pure de la programmation fonctionnelle.

&nbsp;
# Conclusion

Pour conclure, nous avons vu dans cet article ce qu’est le **design pattern container / presentational**. Nous avons vu que ce design pattern consiste a **séparer** la partie **récupération** / **traitement** de données de la partie **présentation** de la vue.

Les avantages d’utiliser ce pattern sont les suivants : la **lisibilité**, la **facilité de débuggage**, la **réutilisabilité** et la **testabilité** des composants. 

Ce design pattern est **facilement applicable** dans vos différentes applications. Il demande très peu d’effort et apporte beaucoup d’avantages.