b) Les modules et collections

Les modules

Introduction

Veuillez lire l'introduction aux modules en JS dans la partie 1 de ce cours : Les modules ES6.

Conventions pour le cours

A partir de maintenant, nous allons créer un fichier pour chaque composant React. Cela permet de mieux structurer le code de nos applications, de rendre le code plus maintenable.

De plus, nous allons créer un dossier par famille de composants.

Nous placerons chaque composant créé dans son propre fichiers : src/components/[ComponentName].jsx

Si un composant est lié à une famille de composants, nous le placerons dans :

  • pour le composant "racine" : src/components/[ComponentName]/index.jsx
  • pour les composants liés au composant "racine" : src/components/[ComponentName]/[ComponentName].jsx

Création de modules

Pour ce tutoriel, veuillez créer une copie du tutoriel components, si nécessaire voici le code du tutoriel components, et l'appeler collections. Changez le nom du projet dans package.json.

Veuillez restructurer toute l'application pour que chaque composant React se trouve dans son propre module, au sein d'un nouveau répertoire /src/components.

Veuillez déplacer App.css et App.tsx dans le dossier /src/components/App et renommer /src/components/App/App.tsx en /src/components/App/index.tsx.

Ensuite, créez trois nouveaux modules & dossiers :

  • /src/components/Header/index.tsx: dans ce module, ajouter le code du composant Header qui se trouve dans le composant App et n'oubliez pas d'exporter Header.
  • /src/components/Main/index.tsx: dans ce module, ajouter le code du composant Main qui se trouve dans le composant App et n'oubliez pas d'exporter Main.
  • /src/components/Footer/index.tsx: dans ce module, ajouter le code du composant Footer qui se trouve dans le composant App et oubliez pas d'exporter Footer.

Maintenant, mettez à jour le composant App afin de supprimer la définition des composants exportés (Header, Main & Footer).

Pensez également à modifier le fichier /src/main.tsx pour mettre à jour le chemin de App lors de l'import (import App from './components/App/index.tsx').

Veuillez vérifier que votre application fonctionne correctement.

A ce stade-ci, nous remarquons que le CSS pour nos nouveaux composants se trouve toujours dans App.css. Cela fonctionne ainsi, car le CSS appliqué à un composant parent s'applique à ses enfants.
Néanmoins, si nous souhaitons réellement avoir des composants entièrement réutilisables, alors il est intéressant de créer un fichier .css pour chaque nouveau composant :

  • /src/components/Header/Header.css : veuillez y ajouter le code CSS associé qui se trouve dans App.css (et l'effacer de App.css). Dans /src/components/Header/index.tsx, veuillez importer la nouvelle feuille de style associée : import "./Header.css";
  • /src/components/Main/Main.css : veuillez y ajouter le code CSS associé qui se trouve dans App.css (et l'effacer de App.css). Dans /src/components/Main/index.tsx, veuillez importer la nouvelle feuille de style associée : import "./Main.css";
  • /src/components/Footer/Footer.css : veuillez y ajouter le code CSS associé qui se trouve dans App.css (et l'effacer de App.css). Dans /src/components/Footer/index.tsx, veuillez importer la nouvelle feuille de style associée : import "./Footer.css";

Ainsi, si nous souhaitons réutiliser le composant Header dans un futur projet, nous n'aurons plus qu'à copier l'entièreté du dossier /src/components/ComponentName dans ce projet.

Les collections de composants

Très souvent nous allons vouloir générer des UI à partir de collections de données.

Par exemple, à cette étape-ci, si nous souhaitions afficher un menu de pizzas dans notre interface, nous allons souhaiter le faire sur base d'un tableau d'objets représentant des pizzas.

Voici à quoi ressemble actuellement le composant App :

tsx
import "./App.css";
import Footer from "../Footer";
import Header from "../Header";
import Main from "../Main";
function App() {
return (
<div className="page">
<Header title="We love Pizza" version={0 + 1} />
<Main />
<Footer />
</div>
);
}
export default App;

Ici, nous décidons que dans la famille de composants Main, nous souhaitons ajouter un composant PizzaMenu qui devra afficher toutes les pizzas de la pizzeria sur base d'un array d'objets.

Voici le code du nouveau composant src/components/Main/PizzaMenu.tsx :

tsx
const pizzas = [
{
id: 1,
title: "4 fromages",
content: "Gruyère, Sérac, Appenzel, Gorgonzola, Tomates",
},
{
id: 2,
title: "Vegan",
content: "Tomates, Courgettes, Oignons, Aubergines, Poivrons",
},
{
id: 3,
title: "Vegetarian",
content: "Mozarella, Tomates, Oignons, Poivrons, Champignons, Olives",
},
{
id: 4,
title: "Alpage",
content: "Gruyère, Mozarella, Lardons, Tomates",
},
{
id: 5,
title: "Diable",
content: "Tomates, Mozarella, Chorizo piquant, Jalapenos",
},
];
const PizzaMenu = () => {
return (
<table>
<thead>
<tr>
<th>Pizza</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{pizzas.map((pizza) => (
<tr>
<td>{pizza.title}</td>
<td>{pizza.content}</td>
</tr>
))}
</tbody>
</table>
);
};
export default PizzaMenu;

pizzas contient un tableau d'objets représentant des pizzas.
La fonction map permet d'itérer sur chacun des objets de pizzas et de générer un nouvel array d'éléments React qui seront rendus par React le moment venu.
Ici la fonction map génère un array de <tr> qui contient les lignes de la future table HTML qui sera rendue par React dans le browser.

Comme le code qui génère les <tr> est du TS/JS, il doit se trouver entre accolades.

Voici le code du composant Main (dans /src/components/Main/index.tsx mis à jour pour utiliser PizzaMenu :

tsx
const Main = () => {
return (
<main>
<p>My HomePage</p>
<p>
Because we love JS, you can also click on the header to stop / start the
music ; )
</p>
<audio id="audioPlayer" controls autoPlay>
<source src={sound} type="audio/mpeg" />
Your browser does not support the audio element.
</audio>
<PizzaMenu />
</main>
);
};

N'oubliez pas d'importer le module PizzaMenu dans Main.

Il est temps de voir le résultat visuel. Wow, nous avons généré dynamiquement un tableau HTML sur base d'une collection de données !

Il nous reste plus qu'à améliorer le design du menu. Pour ce faire, nous pouvons le faire à l'aide d'un nouveau fichier /src/components/Main/PizzaMenu.css dans lequel nous allons créer une classe :

css
.pizza-menu {
margin: 0 auto;
padding: 5px;
border: 1px solid black;
background-color: red;
}

Cette classe devrait nous permettre de centrer le tableau, d'ajouter une bordure et d'avoir un background rouge.

Pour appliquer cette classe, il faut mettre à jour le composant PizzaMenu en important PizzaMenu.css et en ajoutant la classe à <table> :

tsx
import "./PizzaMenu.css";
// Definition of pizzas ...
const PizzaMenu = () => {
return (
<table className="pizza-menu">
//...

Veuillez constater le résultat visuel du site de la pizzeria ! C'est pas mal !

⚡️ Veuillez jeter un œil à la console de votre browser... Il y a un sérieux message d'avertissement : "Each child in a list should have a unique "key" prop.".

L'attribut "key"

React utilise l'attribut key d'elements React qui se trouve dans un array afin de déterminer comment mettre à jour la vue générée par un composant quand il est "re-render".

Il est donc important d'ajouter une clé. Mettons à jour le code de PizzaMenu :

tsx
{pizzas.map((pizza) => (
<tr key={pizza.id}>
<td>{pizza.title}</td>
<td>{pizza.content}</td>
</tr>
))}

Notre application ne contient plus de messages d'avertissement.

Exercice 2.3 : Les modules & collection

Nous allons continuer le projet de votre exercice précédent qui se trouve dans le dossier /exercises/2.1-2-3-4 dans votre git repo.

Notre client nous a donné une nouvelle version des données d'entrée du composant App,ainsi qu'une idée du résultat du composant App :

tsx
const App = () => {
const pageTitle = "Informations sur les films dans les cinémas";
const cinema1Name = "UGC DeBrouckère";
const moviesCinema1 = [
{
title: "HAIKYU-THE DUMPSTER BATTLE",
director: "Susumu Mitsunaka",
},
{
title: "GOODBYE JULIA",
director: "Mohamed Kordofani",
},
{
title: "INCEPTION",
director: "Christopher Nolan",
},
{
title: "PARASITE",
director: "Bong Joon-ho",
},
];
const cinema2Name = "UGC Toison d'Or";
const moviesCinema2 = [
{
title: "THE WATCHERS",
director: "Ishana Night Shyamalan",
},
{
title: "BAD BOYS: RIDE OR DIE",
director: "Adil El Arbi, Bilall Fallah",
},
{
title: "TENET",
director: "Christopher Nolan",
},
{
title: "THE IRISHMAN",
director: "Martin Scorsese",
},
];
return (
<div>
<PageTitle title={pageTitle} />
<Cinema name={cinema1Name} movies= {moviesCinema1} />
<Cinema name={cinema2Name} movies={moviesCinema2} />
</div>
);
};

Veuillez mettre à jour le composant Cinema pour prendre en compte ces nouvelles props.

De plus, vous devez appliquer les conventions vues dans le cours : chaque composant React doit se trouver dans son propre module.

🤝 Tips :

  • Pour définir comme type un array d'un certain type en TS, il suffit d'ajouter des crochets [] à la suite du type. Par exemple, pour un array de Movie : movies: Movie[];

Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.3.

Exercice 2.3b : Encore une collection

Veuillez créer un nouveau projet dans le dossier /exercises/2.3b dans votre git repo.

Nous avons reçu une application mal structurée où tout le code se trouve dans le composant App :

tsx
const App = () => {
const title = "Welcome to My App";
const name1 = "Alice";
const age1 = 25;
const name2 = "Bob";
const age2 = 30;
const name3 = "Charlie";
const age3 = 35;
const footerText = "© 2023 My App";
return (
<div>
<h1>{title}</h1>
<div>
<h2>{name1}</h2>
<p>Age: {age1}</p>
</div>
<div>
<h2>{name2}</h2>
<p>Age: {age2}</p>
</div>
<div>
<h2>{name3}</h2>
<p>Age: {age3}</p>
</div>
<footer>{footerText}</footer>
</div>
);
};
export default App;

Dans votre nouveau projet, vous allez maintenant commencer par copier / coller le code donné et voir que l'application fonctionne. Vous allez maintenant faire un refactor de cette application afin d'avoir un code propre et bien structuré.

Veuillez identifier les parties du code qui peuvent être transformées en composants : Le titre de la page, les cartes utilisateur et le pied de page.

Veuillez créer des fichiers pour chaque composant, importer les composants dans App et les utiliser.

Veuillez aussi restructurer les données utilisées pour les cartes utilisateur en un tableau d'objets.

Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.3b.

Passer du TSX comme enfant : props.children

Parfois nous souhaitons créer un composant à partir d'autres composants.
Mais comment créer un composant de ce style là (DrinkMenu) qui recevrait des composants enfants (DrinkCard) ?

tsx
<DrinkMenu title="Notre Menu de Boissons">
<DrinkCard
title="Coca-Cola"
image="https://media.istockphoto.com/id/1289738725/fr/photo/bouteille-en-plastique-de-coke-avec-la-conception-et-le-chapeau-rouges-d%C3%A9tiquette.jpg?s=1024x1024&w=is&k=20&c=HBWfROrGDTIgD6fuvTlUq6SrwWqIC35-gceDSJ8TTP8="
>
<p>Volume: 33cl</p>
<p>Prix: 2,50 €</p>
</DrinkCard>
<DrinkCard
title="Pepsi"
image="https://media.istockphoto.com/id/185268840/fr/photo/bouteille-de-cola-sur-un-fond-blanc.jpg?s=1024x1024&w=is&k=20&c=xdsxwb4bLjzuQbkT_XvVLyBZyW36GD97T1PCW0MZ4vg="
>
<p>Volume: 33cl</p>
<p>Prix: 2,50 €</p>
</DrinkCard>
<DrinkCard
title="Eau Minérale"
image="https://media.istockphoto.com/id/1397515626/fr/photo/verre-deau-gazeuse-%C3%A0-boire-isol%C3%A9.jpg?s=1024x1024&w=is&k=20&c=iEjq6OL86Li4eDG5YGO59d1O3Ga1iMVc_Kj5oeIfAqk="
>
<p>Volume: 50cl</p>
<p>Prix: 1,50 €</p>
</DrinkCard>
</DrinkMenu>

Nous allons créer un composant DrinkCard qui encapsule du contenu (JSX) via props.children. Dans /src/components/Main/DrinkCard.tsx, veuillez ajouter :

tsx
interface DrinkCardProps {
title: string;
image: string;
children: React.ReactNode;
}
const DrinkCard = (props: DrinkCardProps) => {
return (
<div className="drink-card">
<img src={props.image} alt={props.title} className="drink-image" width="50"/>
<h2>{props.title}</h2>
<div className="drink-details">{props.children}</div>
</div>
);
};
export default DrinkCard;

props.children permet d'encapsuler des détails spécifiques sur chaque boisson (volume, prix, etc.). Nous utilisons React.ReactNode comme type pour children car ça peut être tout type d'élément React.

Nous allons maintenant créer un composant DrinkMenu qui encapsule aussi du contenu (JSX) via props.children. Dans /src/components/Main/DrinkMenu.tsx, veuillez ajouter :

tsx
interface DrinkMenuProps {
title: string;
children: React.ReactNode;
}
const DrinkMenu = (props: DrinkMenuProps) => {
return (
<div className="drink-menu">
<h4>{props.title}</h4>
<div className="drink-items">{props.children}</div>
</div>
);
};
export default DrinkMenu;

props.children va permettre d'encapsuler plusieurs composants DrinkCard.

💭 Attention qu'ici, comme les children sont n'importe quel type d'élément React (React.ReactNode), il serait possible d'ajouter n'importe quoi... Comme nous codons en TS, nous pourrions tenter d'utiliser sa force de pouvoir typer de manière stricte, et n'autoriser :

  • que les children de type DrinkCard s'il n'y a qu'un élément passé à DrinkMenu
  • que les children de type array de DrinkCard

Voila comment on mettrait à jour DrinkMenuProps :

tsx
import { ReactElement } from "react";
import DrinkCard from "./DrinkCard";
interface DrinkMenuProps {
title: string;
children: ReactElement<typeof DrinkCard> | ReactElement<typeof DrinkCard>[];
}

Néanmoins, en TS, tous les éléments JSX ont le type JSX.Element. Il n'est donc pas possible de simplement utiliser le typage stricte pour impose le type des éléments de props.children.

Nous allons maintenant utiliser les composants DrinkMenu et DrinkCard pour afficher un menu de boissons. Dans le composant Main (src/components/Main/index.tsx), veuillez ajouter :

tsx
<DrinkMenu title="Notre Menu de Boissons">
<DrinkCard
title="Coca-Cola"
image="https://media.istockphoto.com/id/1289738725/fr/photo/bouteille-en-plastique-de-coke-avec-la-conception-et-le-chapeau-rouges-d%C3%A9tiquette.jpg?s=1024x1024&w=is&k=20&c=HBWfROrGDTIgD6fuvTlUq6SrwWqIC35-gceDSJ8TTP8="
>
<p>Volume: 33cl</p>
<p>Prix: 2,50 €</p>
</DrinkCard>
<DrinkCard
title="Pepsi"
image="https://media.istockphoto.com/id/185268840/fr/photo/bouteille-de-cola-sur-un-fond-blanc.jpg?s=1024x1024&w=is&k=20&c=xdsxwb4bLjzuQbkT_XvVLyBZyW36GD97T1PCW0MZ4vg="
>
<p>Volume: 33cl</p>
<p>Prix: 2,50 €</p>
</DrinkCard>
<DrinkCard
title="Eau Minérale"
image="https://media.istockphoto.com/id/1397515626/fr/photo/verre-deau-gazeuse-%C3%A0-boire-isol%C3%A9.jpg?s=1024x1024&w=is&k=20&c=iEjq6OL86Li4eDG5YGO59d1O3Ga1iMVc_Kj5oeIfAqk="
>
<p>Volume: 50cl</p>
<p>Prix: 1,50 €</p>
</DrinkCard>
</DrinkMenu>

Veuillez vous assurer que visuellement tout fonctionne.
Ca n'est pas des plus beaux, mais nous n'irons pas plus loin pour ce tutoriel-ci.

Si nécessaire, vous pouvez trouver le code associé à ce tutoriel ici : collections.

Exercice 2.4 : Utilisation de props.children & css

Nous allons continuer le projet de votre exercice précédent qui se trouve dans le dossier /exercises/2.1-2-3-4 dans votre git repo.

Veuillez créer deux nouveaux composants : Header & Footer. Il doit être possible :

  • d'ajouter n'importe quel type de contenu dans ces deux composants en tant qu'enfants.
  • d'afficher un logo (une image) dont l'URL est à passer en propriété.

Veuillez utiliser vos nouveaux composants pour améliorer votre application web et afficher un header et un footer.

N'oubliez pas d'appliquer les conventions vues dans le cours : chaque composant React doit se trouver dans son propre module,...

Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.4.

🤝 Tips :

  • Pour vos images, n'hésitez pas à aller sur le site https://unsplash.com/ ou https://www.istockphoto.com/.
    Une fois une image trouvée, vous pouvez cliquer dessus, puis clic droit pour récupérer l'adresse du lien et l'utiliser dans votre code ; )

🍬 Challenge optionnel : peaufinage du layout de la page

S'il vous reste du temps, n'hésitez pas à peaufiner le layout de votre page et de vos composants à l'aide de CSS.

Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.4-bonus.

Exercice 2.4b : Encore des composants

Veuillez créer un nouveau projet ("from scratch") dans le dossier /exercises/ex2.4b de votre git repo en utilisant les technos Vite + React + TS + SWC.

Veuillez créer un composant React permettant d'afficher des informations de base sur un utilisateur, sous forme d'une carte, sur base de ces props :

  • son nom
  • son âge
  • si l'utilisateur est en ligne ou non

Le nom de l'utilisateur doit être affiché en tête, avec une taille de police plus grande.

L'âge doit être affiché en dessous du nom.

Si l'utilisateur est en ligne, afficher le texte "En ligne" en vert. S'il est hors ligne, afficher le texte "Hors ligne" en rouge.

Veuillez créer trois éléments de votre nouveau composant React dans le composant App en passant à chaque fois des valeurs différentes pour les props.

N'oubliez pas d'appliquer les conventions vues dans le cours : chaque composant React doit se trouver dans son propre module,...

🤝 Tips :

  • Pour faire de l'affichage conditionnel en React, vous pouvez utiliser un ternary operator ou un if dans le JSX.
  • Par exemple, pour ajouter une classe CSS conditionnellement, vous pouvez utiliser une expression ternaire :
tsx
return ( <div className={isOnline ? "online" : "offline"}>
{isOnline ? "En ligne" : "Hors ligne"}
</div> );
  • Avec un if, vous pourriez faire la même chose, mais c'est plus long, il faut deux blocs return :
tsx
if (isOnline) {
return <div className="online">En ligne</div>;
} else {
return <div className="offline">Hors ligne</div>;
}

Une fois tout fonctionnel, veuillez faire un commit avec le message suivant : new:ex2.4b.