UX d’un panier d’achat en React : mises à jour optimistes, annulation et argent en unités mineures
Un panier React instantané et toujours juste : l’argent en unités mineures, des quantités optimistes avec retour en arrière, des requêtes fusionnées, l’annulation et la progression vers la livraison gratuite.
Preet Suthar · · 6 min de lecture
- React
- Next.js
- E-commerce
- UX
Un panier est la première partie d’une boutique qui doit paraître instantanée et être correcte en même temps. On appuie sur plus et le chiffre doit changer tout de suite, pas après un aller-retour. Mais c’est le serveur qui détient le stock, les prix et les remises, et il lui arrivera de dire non.
Voici comment je construis un panier en React et Next.js qui fait les deux : l’argent stocké en entiers, des mises à jour optimistes qui reviennent proprement en arrière, des requêtes fusionnées, l’annulation au lieu des boîtes de confirmation, et une barre de livraison gratuite qui ne ment pas.
Commencer par l’argent
Avant toute UI, réglez la façon dont vous stockez l’argent, sinon chaque total à partir de là sera légèrement faux.
Cela donne 0.30000000000000004, et un panier additionne beaucoup de nombres. Stockez les montants en entiers dans la plus petite unité de la devise, les cents pour l’USD et les yens pour le JPY, qui n’a pas d’unité mineure. C’est aussi ce que Stripe et la plupart des API de paiement envoient et attendent.
formatMoney({ amount: 4800, currency: "USD" }, "en-US") donne $48.00, et le même montant en JPY donne ¥4,800. Intl.NumberFormat gère les symboles, les séparateurs de milliers et les décimales selon la locale, donc 48,00 € en Allemagne est offert. Pour quelques devises, l’unité mineure des prestataires de paiement diffère de la norme ISO, alors vérifiez une fois la liste de votre prestataire.
Refuser d’additionner deux devises n’est pas de la paranoïa. Cela attrape le bug où un panier en EUR reçoit un tarif de livraison en USD.
Des mises à jour optimistes qui savent revenir en arrière
Le panier doit changer dès qu’on appuie sur plus : quantité, total de la ligne et sous-total. La requête s’exécute en arrière-plan. Si elle échoue, la ligne revient à la dernière quantité confirmée par le serveur et explique pourquoi sous la ligne, par exemple « Plus que 2 en stock en Sand / Large ».
Le useOptimistic de React 19 convient bien ici. La valeur optimiste s’affiche pendant la transition, et React l’abandonne quand la transition se termine, donc un échec revient en arrière sans code supplémentaire :
Deux règles le gardent honnête. Le serveur renvoie le panier entier après chaque changement, et ce panier remplace le vôtre, donc les totaux, les remises et la livraison viennent toujours d’un seul endroit. Et l’erreur de la ligne reste affichée jusqu’à ce que l’acheteur modifie à nouveau cette ligne, parce qu’un message qui disparaît avant d’être lu équivaut à aucun message.
Dans Next.js, updateLine peut être une server action. En production, Next.js masque les messages des erreurs levées dans les server actions, donc renvoyez { ok: false, message } pour les erreurs que vous voulez afficher au lieu de lever une exception.
Fusionner les appuis rapides
Quelqu’un qui veut cinq exemplaires d’un article appuie quatre fois sur plus en une seconde. Envoyer quatre requêtes crée quatre occasions d’échouer et quatre réponses qui peuvent arriver dans le désordre. Envoyez plutôt une seule requête avec la quantité finale.
Envoyez la quantité absolue, jamais « plus un ». Une valeur absolue peut être relancée sans risque et reçue en retard sans risque. Si deux requêtes pour la même ligne se chevauchent malgré tout, ignorez toute réponse plus ancienne que la dernière requête envoyée.
Annuler plutôt que « Êtes-vous sûr ? »
Une boîte de confirmation à la suppression ajoute une étape à chaque retrait pour se protéger d’une erreur rare. Annuler protège de la même erreur et ne coûte rien quand il n’y en a pas.
Réduisez la ligne retirée à une courte rangée, « Ring Vase retiré · Annuler », pendant environ cinq secondes, et faites remonter les lignes suivantes à leur place. Annuler remet la ligne là où elle était, pas en bas. Vous pouvez la retirer tout de suite sur le serveur et la rajouter en cas d’annulation, ou attendre que la rangée expire. Retirer tout de suite garde le panier du serveur correct si l’onglet se ferme, c’est donc ce que je préfère, avec la position d’origine conservée côté client.
Annoncez la suppression via une live region polie et placez le focus à un endroit logique, sur le bouton Annuler ou la ligne suivante, plutôt que de le laisser retomber sur la page.
Les lignes devenues obsolètes
Un panier peut rester ouvert dans un onglet pendant des jours. Quand il est rechargé, le serveur doit signaler les lignes dont le prix a changé depuis leur ajout (« Prix baissé à $58 ») et les lignes épuisées. Une ligne épuisée bloque le checkout tant qu’elle n’est pas retirée, et la raison est affichée sur la ligne elle-même, pas dans un toast.
Une barre de livraison gratuite qui dit la vérité
« Plus que $12.00 pour la livraison gratuite » est l’un des rares messages de panier que les gens apprécient vraiment. Deux choses à soigner :
- Utilisez le même sous-total que le serveur. Si la livraison gratuite est calculée après les remises, la barre doit l’être aussi, sinon la barre annonce que vous y avez droit et le checkout facture la livraison.
- Célébrez une seule fois. Jouez un court moment quand le seuil est franchi, puis affichez un discret « Livraison gratuite ». Ne le rejouez pas à chaque rendu ni à chaque ouverture du tiroir.
Utilisez un vrai élément de progression avec un libellé texte, pour que la barre se lise « $138 sur $150 » plutôt que comme une bande colorée.
Le bouton d’ajout au panier et le badge
Les petits détails que l’acheteur voit sur chaque page produit :
- Le bouton garde sa largeur en passant du libellé au spinner puis à la coche, pour que rien à côté ne saute.
- Le spinner attend environ 300 ms avant d’apparaître. La plupart des ajouts se terminent plus vite et n’en ont jamais besoin.
- Le badge du panier ne change que le chiffre qui a changé, et le tiroir s’ouvre ou une courte confirmation s’affiche, pour que les gens sachent où est passé l’article.
Une checklist
- L’argent en entiers d’unités mineures, formaté avec
Intl.NumberFormat. - N’additionnez jamais deux devises.
- Des quantités optimistes, ramenées au dernier panier confirmé avec un message sur la ligne.
- Les appuis rapides fusionnés en une seule requête avec la quantité finale.
- Annulation de la suppression pendant environ cinq secondes, avec restauration de la position de la ligne.
- Prix obsolètes et lignes épuisées signalés sur la ligne, avec blocage du checkout si nécessaire.
- Progression vers la livraison gratuite calculée sur le même sous-total que le serveur.
Les blocs HextaUI pour ça
Ce sont les règles derrière le bloc Cart de HextaUI Pro. Il comprend un AddToCartButton, un CartTrigger avec un compteur en direct et un CartDrawer qui glisse depuis la droite sur ordinateur et depuis le bas sur mobile, plus un CartPanel pour une page panier complète. Les appuis rapides sont fusionnés en une seule requête, un callback en échec ramène la ligne en arrière et affiche votre message, les lignes retirées se réduisent en une rangée Annuler pendant cinq secondes, et la même logique est exportée sous le nom useOptimisticCart pour vos propres mises en page. Ses exemples couvrent la Storefront API de Shopify et les server actions de Next.js.
Order Summary reprend la même structure de panier pour le checkout, avec les codes de réduction, les taxes estimées et les lignes dont le prix a changé. Tous deux utilisent les helpers monétaires de Price, qui travaillent en unités mineures entières et refusent d’additionner deux devises. Vous pouvez tous les essayer dans les aperçus interactifs.
Une fois le panier terminé, l’étape suivante est le checkout : un checkout en une page en React et Next.js.
FAQ
Faut-il stocker les prix en flottants ? Non. Stockez des entiers dans l’unité mineure de la devise et ne divisez qu’au moment du formatage.
Les mises à jour du panier doivent-elles être optimistes ? Oui pour la quantité et la suppression, à condition que le panier du serveur remplace le vôtre après chaque changement et que les échecs soient annulés avec une explication.
Annuler ou boîte de confirmation pour retirer un article ? Annuler. C’est plus rapide quand les gens le veulent vraiment, et tout aussi sûr quand ce n’est pas le cas.
Comment éviter une requête à chaque appui sur plus ? Attendez quelques centaines de millisecondes après le dernier appui et envoyez la quantité finale.