Aller au contenu principal

Module 5 – Optimisation UI/UX (loading, feedback)

Niveau 5.1 – Laravel Livewire


wire:loading, wire:target et les modificateurs (.remove, .class, .attr) pour afficher un état « en cours » et désactiver les boutons pendant l’action. En fin de module : skeletons, wire:transition, accessibilité.


Objectif

À la fin de ce module, vous saurez :

  • Afficher un indicateur de chargement pendant qu’une action Livewire s’exécute (sauvegarde, suppression, recherche) avec wire:loading.
  • Cibler une action précise avec wire:target pour n’afficher le loading que pour un bouton ou une méthode donnée (éviter les messages génériques qui s’affichent partout).
  • Utiliser wire:loading.remove, wire:loading.class et wire:loading.attr pour masquer du contenu, appliquer des classes CSS ou modifier des attributs (ex. désactiver un bouton) pendant le chargement.
  • Appliquer des bonnes pratiques : skeleton loaders, désactivation des boutons pendant la requête, debounce sur les champs pour limiter la sensation de lenteur.

Pourquoi le feedback utilisateur est important

Quand l’utilisateur clique sur « Enregistrer » ou « Rechercher », une requête AJAX part vers le serveur. Sans feedback visuel, l’utilisateur ne sait pas si l’action a bien été prise en compte ; il peut recliquer (double soumission) ou penser que l’app est figée. Livewire fournit des directives pour afficher un état « en cours » pendant qu’une action est en cours d’exécution côté serveur.


wire:loading : afficher un indicateur pendant l’action

wire:loading affiche son contenu uniquement pendant qu’une requête Livewire est en cours (entre le clic/soumission et la réponse du serveur).

Exemple simple :

<button wire:click="save">Enregistrer</button>
<div wire:loading>
Enregistrement en cours...
</div>

Dès que l’utilisateur clique sur « Enregistrer », le texte « Enregistrement en cours... » apparaît ; il disparaît quand la réponse revient et le DOM est mis à jour.

Si vous avez plusieurs boutons (ex. « Enregistrer » et « Supprimer »), sans précision toute requête Livewire du composant afficherait tous les blocs wire:loading. Pour éviter cela, on cible l’action avec wire:target.


wire:target : cibler une action précise

wire:target="nomMethode" restreint le wire:loading à l’action qui appelle la méthode nomMethode. Ainsi, le loading « Sauvegarde... » ne s’affiche que lors du clic sur le bouton qui déclenche save(), et pas lors d’un delete().

Exemple :

<button wire:click="save">Enregistrer</button>
<div wire:loading wire:target="save">
Sauvegarde...
</div>

<button wire:click="delete">Supprimer</button>
<div wire:loading wire:target="delete">
Suppression...
</div>
  • wire:loading wire:target="save" : visible uniquement pendant l’exécution de save().
  • wire:loading wire:target="delete" : visible uniquement pendant l’exécution de delete().

Vous pouvez aussi cibler wire:submit (soumission de formulaire) si votre méthode est appelée via wire:submit="save" : wire:target="save" fonctionne de la même façon.


wire:loading.remove et wire:loading.class

Parfois vous préférez masquer un élément pendant le chargement plutôt qu’afficher un message séparé :

  • wire:loading.remove : l’élément qui porte cette directive est retiré du DOM pendant le chargement (et réaffiché à la fin).
  • wire:loading.class="nom-classe" : la classe CSS est ajoutée à l’élément pendant le chargement (ex. opacity-50 pour griser un bouton, ou pointer-events-none pour désactiver les clics).

Exemples :

<span wire:loading.remove>Contenu normal</span>
<span wire:loading>Chargement...</span>

Pendant la requête : « Contenu normal » disparaît, « Chargement... » apparaît. À la fin, l’inverse.

<button wire:loading.class="opacity-50 cursor-not-allowed" wire:click="submit">
Envoyer
</button>

Pendant submit(), le bouton devient semi-transparent et le curseur indique qu’il est désactivé. Vous pouvez combiner avec wire:target="submit" si vous avez plusieurs boutons.

wire:loading.attr : pour modifier un attribut (ex. disabled) pendant le chargement :

<button wire:loading.attr="disabled" wire:click="save">Enregistrer</button>

Le bouton reçoit disabled pendant l’action, ce qui évite les double-clics.


Exemple complet : formulaire avec feedback

Formulaire avec bouton « Enregistrer » qui affiche « En cours... » et se désactive pendant la sauvegarde.

Vue :

<form wire:submit="save">
<input type="text" wire:model="name">
@error('name') <span class="text-red-600">{{ $message }}</span> @enderror

<button type="submit" wire:loading.attr="disabled" wire:target="save">
<span wire:loading.remove wire:target="save">Enregistrer</span>
<span wire:loading wire:target="save">Enregistrement...</span>
</button>
</form>
  • wire:loading.attr="disabled" : le bouton est désactivé pendant save(), ce qui évite les doubles soumissions.
  • Le texte du bouton bascule de « Enregistrer » à « Enregistrement... » pendant l’action.

Bonnes pratiques

  • Toujours cibler avec wire:target dès qu’il y a plusieurs actions dans le composant, pour que l’utilisateur comprenne quelle action est en cours.
  • Désactiver les boutons (wire:loading.attr="disabled" ou wire:loading.class) pendant l’action pour éviter les double-clics et les requêtes en double.
  • Pour les listes ou tableaux qui se rechargent (filtres, pagination), un skeleton (wire:loading sur un bloc avec des placeholders gris) améliore la perception de la réactivité.
  • Limiter les requêtes : utiliser debounce sur les champs de recherche (module 3) et lazy ou defer quand c’est pertinent, pour que l’interface ne semble pas lente à chaque frappe.

Récap des directives loading :

DirectiveEffet
wire:loadingAffiche le contenu de l’élément uniquement pendant une requête.
wire:loading wire:target="save"Idem, mais seulement pendant l’action save.
wire:loading.removeMasque l’élément pendant le chargement.
wire:loading.class="opacity-50"Ajoute la classe CSS pendant le chargement.
wire:loading.attr="disabled"Ajoute l’attribut (ex. disabled) pendant le chargement.

Sans wire:target, tout wire:loading réagit à n’importe quelle action du composant. Dès qu’il y a plusieurs boutons (Enregistrer, Supprimer, Rechercher), toujours cibler avec wire:target.


Pièges à éviter

  • Loading générique : un seul bloc « Chargement... » sans wire:target sur une page avec plusieurs actions : l’utilisateur ne sait pas quelle action est en cours. Toujours wire:target="nomMethode" pour chaque bloc ou bouton.
  • Bouton non désactivé : sans wire:loading.attr="disabled" (ou .class pour griser), l’utilisateur peut cliquer plusieurs fois sur « Enregistrer » et déclencher plusieurs soumissions. Désactiver le bouton pendant l’action évite les doublons.
  • Texte du bouton qui ne change pas : préférer « Enregistrement... » pendant le chargement (avec wire:loading / wire:loading.remove sur deux spans) pour que l’utilisateur voie clairement que l’action est en cours.

À retenir

  • wire:loading : affiche le contenu pendant une requête Livewire. wire:target="nomMethode" pour ne l’afficher que pour cette action (indispensable dès qu’il y a plusieurs boutons).
  • wire:loading.remove : masquer l’élément pendant le chargement. wire:loading.class="opacity-50" : griser un bouton. wire:loading.attr="disabled" : désactiver le bouton (évite les double-clics).
  • Toujours donner un feedback clair (texte « En cours... » ou état visuel) et éviter les double soumissions en désactivant le bouton ou en ciblant correctement le loading.

Checklist feedback utilisateur

  • Chaque bouton d’action (Enregistrer, Supprimer, Rechercher) a un wire:loading associé avec wire:target="nomMethode" pour que l’utilisateur sache quelle action est en cours.
  • Le bouton de soumission est désactivé pendant l’action (wire:loading.attr="disabled") pour éviter les double-clics.
  • Le texte du bouton ou un message à côté indique l’état « en cours » (ex. « Enregistrement... ») pendant le chargement.
  • Sur les listes ou tableaux qui se rechargent (filtres, pagination), un skeleton ou un indicateur générique améliore la perception (optionnel mais recommandé en production).

Approfondissement

  • Skeleton loader : au lieu d’afficher « Chargement... » en texte, affichez un bloc avec des placeholders (lignes grises, rectangles) qui imitent la structure du contenu. wire:loading sur ce bloc, wire:loading.remove sur le contenu réel. L’utilisateur perçoit moins d’attente car la mise en page reste stable.
  • wire:transition (Livewire 3) : vous pouvez envelopper un wire:loading dans une transition pour un fondu ou un slide. Consultez la doc Livewire pour la syntaxe exacte (entrée/sortie du DOM avec transition CSS).
  • Accessibilité : en plus de disabled, ajoutez aria-busy="true" pendant le chargement (via wire:loading.attr="aria-busy" ou une classe qui ajoute l’attribut) pour que les lecteurs d’écran annoncent que l’action est en cours. Annoncer le succès (message flash ou role="alert") après une action importante.
  • Performance perçue : sur des actions très rapides (< 200 ms), un indicateur qui clignote peut être plus gênant qu’utile. Vous pouvez n’afficher le loading qu’après un délai (ex. 300 ms) avec un peu de JavaScript/Alpine ; avancé et optionnel.

À retenir

  • wire:loading + wire:target="nomMethode" pour cibler l’action ; wire:loading.attr="disabled" pour désactiver le bouton ; wire:loading.remove / .class pour masquer ou styler.
  • Toujours cibler dès qu’il y a plusieurs boutons ; toujours désactiver le bouton pendant l’action pour éviter les doubles soumissions.

Dans le prochain module, nous intégrons Eloquent dans les composants : listes paginées, recherche, tri, et optimisation des requêtes (N+1, with(), select()).