Action et Loader
Notre page principale affiche la liste des logements.
Alors on a furieusement envie de cliquer sur un bouton réserver pour appeler notre commande book. Malheureusement, il ne se passe rien.
Mais qu'attendait-on exactement ?
Reprenons notre test :
const during = await app.dependencies.bookings.getAvailableAccommodations(
Stay.parse({ from: "2024-06-01", to: "2024-06-03" }).value
);
expect(during.some(accommodation => accommodation.id === "accommodation-1")).toBe(false);Après avoir réalisé une réservation d'un logement sur une période, le logement ne figure plus dans la liste des logements disponibles à cette période. Donc le logement réservé devrait "disparaître" de la liste.
Pourquoi n'est-ce pas ce qu'il se passe ?
Boucler la boucle
Rappelons-nous notre schéma de principe de l'animation de notre page :
LOADER --- alimente ---> RENDER
| |
re-déclenche déclenche
^ |
| |
----------- ACTION <------Après une action, il nous faut actualiser la vue en invoquant à nouveau le loader.
En l'état, notre code n'implémente pas ce lien : il nous faut boucler la boucle.
Pour cela, commençons par modifier notre hook pour exporter un moyen de recharger les données, au travers d'une fonction refresh :
export function useAccommodations() {
// Nos 2 états sur la page
const [loading, setLoading] = useState(false);
const [accommodations, setAccommodations] = useState([]);
// Une fonction qui orchestre les changements d'état
const loadAccommodations = async () => {
setLoading(true);
setAccommodations(await loader());
setLoading(false);
};
// Un effet
useEffect(() => {
loadAccommodations();
}, []);
return { accommodations, loading, refresh: loadAccommodations };
}Et utilisons cette fonction pour rafraîchir la page à condition que l'action réussisse :
function HomePage() {
const { accommodations, loading, refresh } = useAccommodations();
const [error, setError] = useState(null);
const onBook = async (accommodationId) => {
const { status, error } = await action(accommodationId);
if (status === "ok") {
setError(null);
await refresh();
} else {
setError(error);
}
};
return (
<Layout loading={loading} error={error}>
{accommodations.map((accommodation) => (
<Accommodation
key={accommodation.id}
accommodation={accommodation}
onBook={() => onBook(accommodation.id)}
/>
))}
</Layout>
);
}Notez ce que nous faisons du cas d'erreur. La tentation est grande d'écrire status === "ok" ? refresh() : {} et de passer à autre chose — mais un échec silencieux, pour l'utilisateur, c'est un bouton qui ne marche pas.
Or nos commandes ne lèvent jamais d'exception : elles retournent un contexte porteur de son erreur. Nous avons donc, en une ligne, de quoi dire à l'utilisateur ce qui s'est passé. Ce serait dommage de le jeter.
C'est le bénéfice différé de la décision prise au chapitre 14 : préférer le résultat à l'exception. Deux mois plus tard, dans une couche complètement différente, l'information est encore là.
La boucle est bouclée : loader, render, action. Notre première page fonctionne.
Il ne reste qu'un détail, en haut de l'écran : ce bandeau de recherche qui ne sert encore à rien.