Les composants
Notre page comporte 2 composants React :
function HomePage() {
const { accommodations, loading } = useAccommodations();
return (
<Layout loading={loading}>
{accommodations.map((accommodation) => (
<Accommodation
key={accommodation.id}
accommodation={accommodation}
onBook={() => action(accommodation.id)}
/>
))}
</Layout>
);
}Nous avons séparé la logique d'animation dans une fonction useAccommodations.
Ainsi, nos composants sont centrés sur une seule responsabilité : l'affichage.
Rôles de nos 2 composants
Le principe de React est la composition. On imbrique des composants dans une structure déclarative pour représenter l'interface graphique attendue.
Le rôle de notre composant Layout est de s'occuper de la disposition générale des éléments de la page.
Et celui de notre composant Accommodation est d'afficher la carte d'un logement de vacances que l'utilisateur peut réserver.
L'échéance de votre démo approche. Et vous n'avez pas le temps de peaufiner le rendu graphique. Alors prenons un raccourci, qui fait désormais le quotidien de tous les développeurs.
Un raccourci
Utilisons ChatGPT pour créer ces 2 composants React.
Voici le prompt :
Ecris le code des composants React Layout et Accommodation pour ma web application de réservation de logements de vacances :
function HomePage() {
const { accommodations, loading } = useAccommodations()
return (<Layout loading={loading}>
{accommodations.map(accommodation => <Accommodation
key={accommodation.id}
accommodation={accommodation}
onBook={() => action(accommodation.id)}
/>)}
</Layout>)
}Le résultat est minimaliste. Et surtout pas très beau.
Alors améliorons cela en ajoutant un screenshot d'un site bien connu :
Améliore les composants pour que ça ressemble à ça
Et là, nous avons bien mieux !
Le Layout possède un bandeau de recherche, qui va nous être utile.
import './Layout.css';
function Layout({ children, loading }) {
return (
<div className="layout">
<header className="header">
<div className="search-bar">
<input type="text" placeholder="Rechercher une destination" />
<input type="text" placeholder="Arrivée" />
<input type="text" placeholder="Départ" />
<input type="text" placeholder="Ajouter des voyageurs" />
<button className="search-button">🔍</button>
</div>
</header>
<main className="main-content">
{loading ? (
<div className="loading">Loading...</div>
) : (
<div className="accommodations-list">{children}</div>
)}
</main>
</div>
);
}
export { Layout };Et le composant Accommodation comporte notre bouton de réservation.
import './Accommodation.css';
function Accommodation({ accommodation, onBook }) {
return (
<div className="accommodation-card">
<img src={accommodation.imageUrl} alt={accommodation.name} className="accommodation-image" />
<div className="accommodation-details">
<div className="accommodation-header">
<h2>{accommodation.name}</h2>
<button className="favorite-button">❤️</button>
</div>
<p className="accommodation-location">{accommodation.location}</p>
<p className="accommodation-host">{accommodation.host}</p>
<p className="accommodation-price">{accommodation.price} € par nuit</p>
<button className="book-button" onClick={onBook}>Réserver</button>
</div>
</div>
);
}
export { Accommodation };Deux corrections ont été nécessaires sur ce que le modèle avait produit, et elles illustrent bien ce qu'il faut surveiller.
Le composant affichait accommodation.dates, un champ qui n'existe nulle part dans notre domaine. Le modèle l'a inventé parce que le screenshot en montrait. Un champ inexistant ne provoque pas d'erreur en React : il affiche du vide, silencieusement. C'est le pire des cas.
Et il mettait la ville en <h2> et le nom du logement en paragraphe. L'inverse de ce qu'attend un lecteur — et de ce qu'attend un moteur de recherche.
Les deux composants ont besoin de leurs feuilles de style Layout.css et Accommodation.css : demandez-les au modèle dans la foulée, ou écrivez-les vous-même.
Notez enfin que le bandeau de recherche du Layout n'est encore branché sur rien. Ses champs sont décoratifs : les dates du séjour restent celles écrites en dur dans notre loader. Nous les brancherons plus tard.
Utilisons ces 2 composants pour voir ce que ça donne.
Notre repository de test
Il reste un problème pour notre démo : nous n'avons pas de listing de logements à louer.
Rappelons-nous notre dépendance MemoryBookingRepository :
export class MemoryBookingRepository {
_bookings = [];
_accommodations = [{ id: "accommodation-1" }, { id: "accommodation-2" }];
...
}Il ne comporte que les 2 identifiants des logements, car nous n'avions besoin de rien de plus.
Étoffons un peu cela avec un fichier fakeAccommodations.js.
export const fakeAccommodations = [
{ id: "accommodation-1", name: "Villa 6 pièces avec piscine", location: "Saint-Rémy-de-Provence", host: "Claire Vasseur", capacity: 8, price: 230, imageUrl: "https://picsum.photos/seed/acc1/400/300" },
{ id: "accommodation-2", name: "Mas provençal rénové", location: "Gordes", host: "Claire Vasseur", capacity: 6, price: 180, imageUrl: "https://picsum.photos/seed/acc2/400/300" },
{ id: "accommodation-3", name: "Studio vue mer", location: "Cassis", host: "Yanis Brahimi", capacity: 2, price: 95, imageUrl: "https://picsum.photos/seed/acc3/400/300" },
{ id: "accommodation-4", name: "Chalet en bord de piste", location: "Le Grand-Bornand", host: "Yanis Brahimi", capacity: 10, price: 320, imageUrl: "https://picsum.photos/seed/acc4/400/300" },
{ id: "accommodation-5", name: "Gîte au calme", location: "Sciotot", host: "Marek Nowak", capacity: 4, price: 110, imageUrl: "https://picsum.photos/seed/acc5/400/300" },
{ id: "accommodation-6", name: "Loft d'architecte", location: "Bordeaux", host: "Marek Nowak", capacity: 3, price: 140, imageUrl: "https://picsum.photos/seed/acc6/400/300" },
];Prenez le temps de varier ces données, plutôt que de dupliquer six fois la même ligne. Des jeux d'essai tous identiques masquent la moitié des bugs d'affichage : un nom trop long qui déborde, un prix à trois chiffres qui casse l'alignement, une ville qui passe à la ligne.
(Le seed dans l'URL de picsum.photos sert exactement à cela : sans lui, chaque rechargement tire une image différente et l'écran clignote.)
Remarquez aussi le champ capacity, que nous ajoutons dès maintenant. Nous en aurons besoin pour l'invariant « le logement ne peut pas accueillir plus de personnes que prévu », resté sur notre liste depuis le chapitre 14.
Et injectons cela dans notre repository de test :
export class MemoryBookingRepository {
_bookings = [];
_accommodations = fakeAccommodations;
...
}Et c'est tout : getAvailableAccommodations sait déjà lire _accommodations, notre page n'a besoin de rien d'autre.
Résistez à l'envie d'ajouter dans la foulée un getAccommodations() qui retournerait toute la liste. Personne ne l'appelle. Une méthode de repository que personne n'appelle est une promesse que vous devrez tenir dans toutes les implémentations à venir — y compris celle en SQL.
L'aperçu de notre première page
Et le résultat n'est pas si mal !

Le code complet de cette étape est disponible ici (opens in a new tab).