8 min

Mises à jour optimistes dans React : sensation de rapidité sans dérive des données

Les mises à jour optimistes dans React peuvent donner l'impression d'immédiateté. Apprenez des méthodes sûres pour concilier la vérité serveur, gérer les échecs et prévenir la dérive des données.

Mises à jour optimistes dans React : sensation de rapidité sans dérive des données

Ce que signifie l'UI optimiste et pourquoi la dérive des données se produit

L'UI optimiste dans React consiste à mettre à jour l'affichage comme si une modification avait déjà réussi, avant que le serveur ne la confirme. Quelqu'un clique sur « J'aime », le compteur augmente immédiatement, et la requête s'exécute en arrière-plan.

Ce retour instantané rend l'application réactive. Sur un réseau lent, c'est souvent la différence entre une sensation « vive » et « est-ce que ça a marché ? »

Le compromis, c'est la dérive des données : ce que l'utilisateur voit peut petit à petit diverger de la vérité sur le serveur. La dérive apparaît généralement sous forme d'incohérences mineures et frustrantes, dépendantes du timing et difficiles à reproduire.

Les utilisateurs remarquent la dérive quand l'interface « change d'avis » plus tard : un compteur saute puis revient en arrière, un élément apparaît puis disparaît après un rafraîchissement, une modification semble prise en compte jusqu'à ce que vous reveniez sur la page, ou deux onglets affichent des valeurs différentes.

Cela se produit parce que l'UI fait une supposition, et que le serveur peut renvoyer une vérité différente. Des règles de validation, du dédoublonnage, des vérifications de permissions, des limites de taux ou un autre appareil modifiant le même enregistrement peuvent tous changer le résultat final. Une cause fréquente est aussi les requêtes qui se chevauchent : une réponse plus ancienne arrive en dernier et écrase l'action plus récente de l'utilisateur.

Exemple : vous renommez un projet en « Q1 Plan » et l'affichez tout de suite dans l'en-tête. Le serveur peut tronquer les espaces, rejeter certains caractères ou générer un slug. Si vous ne remplacez jamais la valeur optimiste par la valeur finale du serveur, l'UI semblera correcte jusqu'au prochain rafraîchissement, quand elle « changera mystérieusement ».

L'UI optimiste n'est pas toujours le bon choix. Soyez prudent (ou évitez-la) pour l'argent et la facturation, les actions irréversibles, les changements de rôle et de permission, les workflows avec règles serveur complexes, ou tout ce qui a des effets secondaires nécessitant une confirmation explicite.

Bien utilisée, l'optimistic UI rend une application instantanée — à condition de prévoir la réconciliation, l'ordre d'application et la gestion des échecs.

Choisissez la source de vérité pour chaque donnée

L'UI optimiste fonctionne mieux quand vous séparez deux types d'état :

  • État serveur : ce que le backend décide en dernier ressort.
  • État UI local : ce que l'utilisateur fait maintenant sur cet écran.

La plupart des dérives commencent quand une supposition locale est traitée comme une vérité confirmée.

Règle simple : si une valeur a une portée métier hors de l'écran courant, le serveur est la source de vérité. Si elle n'affecte que le comportement de l'écran (ouvert/fermé, input focus, texte de brouillon), gardez-la locale.

En pratique, conservez la vérité serveur pour des éléments comme les permissions, les prix, les soldes, l'inventaire, les champs calculés ou validés, et tout ce qui peut changer ailleurs (un autre onglet, un autre utilisateur). Gardez l'état UI local pour les brouillons, les flags « en train d'éditer », les filtres temporaires, les lignes développées et les bascules d'animation.

Certaines actions sont « sûres à deviner » parce que le serveur les accepte presque toujours et qu'elles sont faciles à inverser, comme marquer un élément comme favori ou basculer une préférence simple.

Quand un champ n'est pas sûr à deviner, vous pouvez quand même donner l'impression de rapidité sans prétendre que le changement est définitif. Conservez la dernière valeur confirmée et ajoutez un signal clair indiquant qu'une validation est en cours.

Par exemple, sur un écran CRM où vous cliquez sur « Mark as paid », le serveur peut le rejeter (permissions, validation, remboursement déjà effectué). Au lieu de réécrire instantanément tous les montants dérivés, mettez à jour le statut avec un discret « Saving… », laissez les totaux inchangés et ne mettez à jour les totaux qu'après confirmation.

Afficher « en attente » sans embrouiller les utilisateurs

Les bons patterns sont simples et cohérents : un petit badge « Saving… » à côté de l'élément modifié, désactiver temporairement l'action (ou la transformer en Annuler) jusqu'à ce que la requête soit résolue, ou marquer visuellement la valeur optimiste comme temporaire (texte plus pâle ou petit spinner).

Rafraîchir après mutation ou patcher localement ?

Si la réponse serveur peut affecter beaucoup d'endroits (totaux, tri, champs calculés, permissions), le rafraîchissement est généralement plus sûr que d'essayer de patcher tout manuellement. Si c'est un petit changement isolé (renommer une note, basculer un flag), patcher localement suffit souvent.

Une règle utile : patcher uniquement la chose modifiée par l'utilisateur, puis refetcher les données dérivées, agrégées ou partagées entre écrans.

Modelez vos données pour que les mises à jour optimistes restent sûres

L'UI optimiste marche quand votre modèle de données distingue clairement ce qui est confirmé et ce qui reste une supposition. Si vous modélisez explicitement cet écart, les moments « pourquoi ça est revenu en arrière ? » deviennent rares.

Donnez à chaque élément une identité stable (même avant le serveur)

Pour les éléments nouvellement créés, attribuez un ID client temporaire (comme temp_12345 ou un UUID), puis remplacez-le par le vrai ID serveur quand la réponse arrive. Cela permet aux listes, à la sélection et à l'état d'édition de se réconcilier proprement.

Exemple : un utilisateur ajoute une tâche. Vous la render immédiatement avec id: "temp_a1". Quand le serveur répond avec id: 981, vous remplacez l'ID en un seul endroit, et tout ce qui est keyé par ID continue de fonctionner.

Suivez « pending vs confirmed » par élément, pas globalement

Un simple flag de chargement au niveau de l'écran est trop grossier. Suivez le statut sur l'élément (ou même sur le champ) qui change. Ainsi vous pouvez afficher une UI en attente discrète, relancer uniquement ce qui a échoué et éviter de bloquer des actions non liées.

Une forme d'objet pratique :

  • id : réel ou temporaire
  • status : pending | confirmed | failed
  • optimisticPatch : ce que vous avez changé localement (petit et spécifique)
  • serverValue : dernière donnée confirmée (ou un confirmedAt)
  • rollbackSnapshot : la valeur confirmée précédente que vous pouvez restaurer

Préférez de petits patches plutôt que de remplacer l'objet entier

Les mises à jour optimistes sont plus sûres quand vous touchez uniquement ce que l'utilisateur a réellement changé (par exemple basculer completed) au lieu de remplacer l'objet entier par une supposée « nouvelle version ». Le remplacement global peut écraser des éditions plus récentes, des champs ajoutés par le serveur ou des changements concurrents.

Étape par étape : un flux de mutation optimiste fiable

Une bonne mise à jour optimiste donne une sensation d'instantanéité, tout en finissant par correspondre à la vérité serveur. Traitez le changement optimiste comme temporaire et conservez assez d'informations pour le confirmer ou l'annuler en toute sécurité.

Exemple : un utilisateur modifie le titre d'une tâche dans une liste. Vous voulez que le titre change tout de suite, mais vous devez aussi gérer les erreurs de validation et le formatage côté serveur.

  1. Appliquez le changement optimiste immédiatement dans l'état local. Stockez un petit patch (ou snapshot) pour pouvoir revenir en arrière.

  2. Envoyez la requête avec un ID de requête (un nombre incrémental ou un ID aléatoire). C'est ainsi que vous associez les réponses à l'action qui les a déclenchées.

  3. Marquez l'élément comme pending. Le pending n'a pas à bloquer l'UI : cela peut être un petit spinner, du texte estompé ou « Saving… ». L'essentiel est que l'utilisateur comprenne que ce n'est pas encore confirmé.

  4. En cas de succès, remplacez les données client temporaires par la version serveur. Si le serveur a ajusté quelque chose (tronqué des espaces, changé la casse, mis à jour des timestamps), mettez à jour l'état local pour correspondre.

  5. En cas d'échec, restaurez seulement ce que cette requête a changé et affichez une erreur locale claire. Évitez de remettre en arrière des parties non liées de l'écran.

Voici une petite forme à suivre (agnostique vis-à-vis des librairies) :

const requestId = crypto.randomUUID();
applyOptimistic({ id, title: nextTitle, pending: requestId });

try {
  const serverItem = await api.updateTask({ id, title: nextTitle, requestId });
  confirmSuccess({ id, requestId, serverItem });
} catch (err) {
  rollback({ id, requestId });
  showError("Could not save. Your change was undone.");
}

Deux détails qui évitent beaucoup de bugs : stockez l'ID de requête sur l'élément tant qu'il est pending, et ne confirmez ou n'annulez que si les IDs correspondent. Cela empêche des réponses plus anciennes d'écraser des éditions plus récentes.

Empêcher les réponses obsolètes d'écraser des actions utilisateur plus récentes

Corriger les réponses hors d'ordre
Demandez à Koder.ai d'ajouter des IDs de requête et d'ignorer les réponses obsolètes pour chaque mutation.

L'UI optimiste se casse quand le réseau répond dans le désordre. Un échec classique : l'utilisateur édite un titre, l'édite encore immédiatement, et la première requête finit en dernier. Si vous appliquez cette réponse tardive, l'UI revient à une valeur plus ancienne.

La solution consiste à traiter chaque réponse comme « peut-être pertinente » et à ne l'appliquer que si elle correspond à l'intention utilisateur la plus récente.

Se protéger contre les réponses hors d'ordre

Un pattern pratique est d'attacher un ID de requête côté client (un compteur) à chaque changement optimiste. Conservez le dernier ID par enregistrement. Quand une réponse arrive, comparez les IDs. Si la réponse est plus ancienne que la dernière, ignorez-la.

Des vérifications de version aident aussi. Si votre serveur renvoie updatedAt, version ou un etag, n'acceptez que les réponses plus récentes que ce que l'UI affiche déjà.

D'autres options à combiner :

  • Annuler les requêtes en cours quand une nouvelle édition commence.
  • Mettre en file les éditions par élément pour qu'une seule requête tourne à la fois.
  • Mettre en pause le refetching en arrière-plan pour qu'il n'écrase pas l'état optimiste en plein edit.

Exemple (garde d'ID de requête) :

let nextId = 1;
const latestByItem = new Map();

async function saveTitle(itemId, title) {
  const requestId = nextId++;
  latestByItem.set(itemId, requestId);

  // optimistic update
  setItems(prev => prev.map(i => i.id === itemId ? { ...i, title } : i));

  const res = await api.updateItem(itemId, { title, requestId });

  // ignore stale response
  if (latestByItem.get(itemId) !== requestId) return;

  // reconcile with server truth
  setItems(prev => prev.map(i => i.id === itemId ? { ...i, ...res.item } : i));
}

Si les utilisateurs tapent vite (notes, titres, recherche), envisagez d'annuler ou de retarder les sauvegardes tant qu'ils n'ont pas fait de pause. Cela réduit la charge serveur et diminue le risque que des réponses tardives provoquent des retours visibles.

Gérer les échecs sans confondre avec des rollbacks

Les échecs sont là où l'UI optimiste peut perdre la confiance des utilisateurs. La pire expérience est un rollback soudain sans explication. Une bonne approche par défaut pour les éditions : gardez la valeur de l'utilisateur à l'écran, marquez-la comme non sauvegardée et affichez une erreur en ligne à l'endroit exact où il a modifié.

Si quelqu'un renomme un projet de « Alpha » à « Q1 Launch », ne le remettez pas automatiquement à « Alpha » à moins que ce soit nécessaire. Gardez « Q1 Launch », ajoutez « Non sauvegardé. Nom déjà pris » et laissez-le corriger.

Préférez les erreurs en ligne aux rollbacks globaux

Le feedback en ligne reste attaché au champ ou à la ligne qui a échoué. Il évite le moment « que s'est-il passé ? » où un toast apparaît mais l'UI change silencieusement en arrière-plan.

Des indications fiables : « Saving… » pendant la requête, « Not saved » en cas d'échec, un léger surlignage de la ligne affectée et un message court expliquant la marche à suivre.

Proposez retry et undo, mais seulement quand ça a du sens

Retry est presque toujours utile. Undo convient aux actions rapides que l'on peut regretter (archiver), mais il peut être déroutant pour des éditions où l'utilisateur veut clairement la nouvelle valeur.

Quand une mutation échoue :

  • Gardez la valeur optimiste visible.
  • Désactivez seulement ce qui doit l'être (souvent juste l'action Sauvegarder).
  • Placez Retry près de l'erreur en ligne.
  • Si vous faites un rollback, rendez-le explicite ("Revert change").

Si vous devez rollback (par ex. permissions modifiées et plus d'accès), expliquez et restaurez la vérité serveur : « Impossible d'enregistrer. Vous n'avez plus les droits d'édition. »

Réconcilier avec la vérité serveur après la réponse

Appliquer la checklist en quelques minutes
Transformez cette checklist en un écran React fonctionnel et itérez en discutant avec Koder.ai.

Traitez la réponse serveur comme un reçu, pas seulement un flag de succès. Après la fin de la requête, réconciliez : conservez ce que l'utilisateur voulait et acceptez ce que le serveur sait mieux.

Refetch vs merge : choisissez l'option la plus sûre

Un refetch complet est le plus sûr quand le serveur a pu changer plus de choses que votre supposition locale. C'est aussi plus facile à raisonner.

Refetch est souvent préférable quand la mutation affecte beaucoup d'enregistrements (déplacer des items entre listes), quand les permissions ou règles de workflow peuvent changer le résultat, quand le serveur renvoie des données partielles, ou quand d'autres clients mettent souvent à jour la même vue.

Si le serveur renvoie l'entité mise à jour (ou suffisamment de champs), merger peut offrir une meilleure expérience : l'UI reste stable tout en acceptant la vérité serveur.

Fusionnez les champs serveur que vous n'avez pas édités

La dérive vient souvent du fait d'écraser des champs détenus par le serveur avec un objet optimiste. Pensez aux compteurs, champs calculés, timestamps et formats normalisés.

Exemple : vous définissez optimistement likedByMe=true et incrémentez likeCount. Le serveur peut dédupliquer les likes et renvoyer un likeCount différent, plus un updatedAt rafraîchi.

Une approche de merge simple :

  1. Partezz de la version client la plus récente (qui peut inclure des éditions ultérieures).
  2. Patcher les champs détenus par le serveur (compteurs, valeurs calculées, timestamps).
  3. Garder les champs édités par l'utilisateur si celui-ci les a modifiés après le début de la requête.

Règles de conflit : soyez explicite

Quand il y a conflit, décidez à l'avance. « Dernière écriture gagne » suffit pour des toggles. La fusion au niveau des champs est meilleure pour des formulaires.

Suivre un flag « dirty since request » par champ (ou un numéro de version local) permet d'ignorer les valeurs serveur pour les champs que l'utilisateur a modifiés après le début de la mutation, tout en acceptant la vérité serveur pour le reste.

Utilisez les erreurs de validation serveur pour guider l'UI

Si le serveur rejette la mutation, préférez des erreurs spécifiques et légères plutôt qu'un rollback surprise. Gardez la saisie de l'utilisateur, mettez en évidence le champ et affichez le message. Les rollbacks de sauvegarde conviennent si l'action ne peut vraiment pas être acceptée (par exemple suppression optimiste refusée par le serveur).

Listes, pagination et autres cas délicats

Les listes sont là où l'UI optimiste est très agréable mais peut facilement casser. Un élément qui change peut affecter l'ordre, les totaux, les filtres et plusieurs pages.

Pour les créations, affichez le nouvel élément immédiatement mais marquez-le comme pending, avec un ID temporaire. Gardez sa position stable pour qu'il ne saute pas.

Pour les suppressions, un pattern sûr est de masquer l'élément tout de suite mais de conserver un enregistrement « fantôme » en mémoire pendant un court instant jusqu'à confirmation serveur. Cela permet l'undo et facilite la gestion des échecs.

Le réordonnancement est délicat car il touche beaucoup d'éléments. Si vous réordonnez de manière optimiste, stockez l'ordre précédent pour pouvoir le restaurer si nécessaire.

Avec la pagination ou l'infinite scroll, décidez où placer les insertions optimistes. Dans un fil, les nouveaux éléments vont généralement en haut. Dans des catalogues triés par le serveur, l'insertion locale peut induire en erreur car le serveur peut placer l'élément ailleurs. Compromis pratique : insérer dans la liste visible avec un badge pending, puis être prêt à déplacer l'élément après la réponse serveur si la clé de tri finale diffère.

Quand un ID temporaire devient un ID réel, dédupliquez par une clé stable. Si vous ne matchez que par ID, vous pouvez montrer deux fois le même élément (temp et confirmé). Gardez une map tempId→realId et remplacez en place pour que la position de scroll et la sélection ne se réinitialisent pas.

Les compteurs et les filtres sont aussi de l'état de liste. Mettez à jour les compteurs de façon optimiste seulement quand vous êtes sûr que le serveur sera d'accord. Sinon, marquez-les comme en rafraîchissement et réconciliez après la réponse.

Erreurs courantes qui causent dérive et bugs

Concevoir d'abord le flux d'état
Utilisez le mode Planification pour cartographier l'état en attente vs confirmé avant d'écrire quoi que ce soit.

La plupart des bugs d'optimistic update ne viennent pas vraiment de React. Ils viennent du fait de traiter un changement optimiste comme « la nouvelle vérité » au lieu d'une supposition temporaire.

Mettre à jour trop, trop vite

Mettre à jour de façon optimiste un objet entier ou un écran entier alors qu'un seul champ a changé élargit le rayon d'impact. Les corrections serveur ultérieures peuvent écraser des éditions non liées.

Exemple : un formulaire de profil remplace tout l'objet user quand vous basculez un réglage. Pendant que la requête est en vol, l'utilisateur modifie son nom. À l'arrivée de la réponse, votre remplacement peut remettre l'ancien nom.

Gardez les patches optimistes petits et ciblés.

Oublier d'effacer l'état “pending”

Une autre source de dérive est d'oublier de supprimer les flags pending après succès ou erreur. L'UI reste à moitié en chargement, et une logique ultérieure peut la considérer encore comme optimiste.

Si vous suivez l'état pending par élément, effacez-le avec la même clé que celle que vous avez utilisée pour le définir. Les IDs temporaires provoquent souvent des éléments « pending fantômes » quand le vrai ID n'est pas mappé partout.

Rollback vers la mauvaise valeur

Les bugs de rollback surviennent quand le snapshot est stocké trop tard ou trop globalement.

Si un utilisateur fait deux éditions rapides, vous pouvez finir par rollback l'édition #2 en utilisant le snapshot d'avant l'édition #1. L'UI saute vers un état que l'utilisateur n'a jamais vu.

Solution : snapshottez la portion exacte que vous restaurerez, et scopez-la à une tentative de mutation spécifique (souvent via l'ID de requête).

Ignorer les échecs partiels et les réécritures serveur

Les sauvegardes réelles sont souvent multi-étapes. Si l'étape 2 échoue (par ex. upload d'image), ne désactivez pas silencieusement l'étape 1. Montrez ce qui a été sauvegardé, ce qui ne l'a pas été, et ce que l'utilisateur peut faire ensuite.

De plus, n'assumez pas que le serveur renverra exactement ce que vous avez envoyé. Les serveurs normalisent le texte, appliquent des permissions, définissent des timestamps, attribuent des IDs et suppriment des champs. Réconciliez toujours à partir de la réponse (ou refetch) au lieu de faire confiance au patch optimiste pour toujours.

Checklist rapide et prochaines étapes pratiques

L'UI optimiste fonctionne quand elle est prévisible. Traitez chaque changement optimiste comme une mini-transaction : il a un ID, un état visible en attente, un swap clair en cas de succès et un chemin d'échec qui ne surprend pas.

Checklist à revoir avant mise en production :

  • Afficher un indicateur en attente clair sur la chose exacte qui a changé.
  • Donner à chaque mutation un ID de requête unique, et ne laisser que la réponse correspondante confirmer ou annuler ce changement.
  • En cas de succès, remplacer les données client temporaires par la réponse serveur (IDs, timestamps, champs calculés), puis effacer l'état pending.
  • En cas d'échec, rendre la récupération évidente : restaurer la valeur précédente quand cela n'embrouillera pas l'utilisateur, ou garder l'édition visible avec une erreur en ligne et un Retry.
  • Prévoir une issue de secours : un « force refetch » facile quand l'état client n'est pas digne de confiance.

Si vous prototypez rapidement, limitez la première version : un écran, une mutation, une mise à jour de liste. Des outils comme Koder.ai (koder.ai) peuvent vous aider à esquisser l'UI et l'API plus vite, mais la même règle s'applique : modélisez pending vs confirmed pour que le client ne perde jamais la trace de ce que le serveur a réellement accepté.

FAQ

Qu'est-ce qu'une mise à jour UI optimiste dans React ?

L'UI optimiste met à jour l'écran immédiatement, avant que le serveur ne confirme le changement. Cela rend l'application plus réactive, mais il faut ensuite concilier avec la réponse serveur pour éviter que l'UI ne dérive de l'état réellement enregistré.

Pourquoi l'UI optimiste provoque-t-elle une dérive des données ?

La dérive des données survient lorsque l'UI garde une supposition optimiste comme si elle était confirmée, alors que le serveur a finalement sauvegardé quelque chose de différent ou a rejeté la modification. Elle apparaît souvent après un rafraîchissement, dans un autre onglet, ou lorsque des réseaux lents font arriver les réponses dans le désordre.

Quand devrais-je éviter l'UI optimiste ?

Évitez ou soyez très prudent avec les updates optimistes pour l'argent, la facturation, les actions irréversibles, les changements de permissions et les workflows avec des règles serveur strictes. Dans ces cas, il est plus sûr d'afficher un état en attente clair et d'attendre la confirmation avant de modifier des totaux ou des accès.

Comment décider ce qui doit être « state serveur » vs « state UI local » ?

Considérez le backend comme la source de vérité pour tout ce qui a une signification métier hors de l'écran courant : prix, permissions, champs calculés, compteurs partagés. Gardez l'état local pour les brouillons, le focus, le flag « en train d'éditer », les filtres temporaires et l'état purement présentatif.

Quelle est la meilleure façon d'afficher « en attente » sans confondre les utilisateurs ?

Affichez un signal petit et cohérent à l'endroit du changement, comme « Saving… », un texte estompé ou un petit spinner. Le but est de montrer que la valeur est temporaire sans bloquer toute la page.

Comment gérer les créations optimistes avant que le serveur n'attribue un vrai ID ?

Utilisez un ID client temporaire (comme un UUID ou temp_...) lors de la création d'un élément, puis remplacez-le par l'ID réel du serveur en cas de succès. Cela garde stables les clés de liste, la sélection et l'état d'édition pour éviter clignotements ou duplications.

Comment dois-je suivre l'état en attente vs confirmé dans mon modèle de données ?

Ne vous fiez pas à un flag global de chargement ; suivez l'état en attente par élément (ou par champ) afin que seule la chose modifiée apparaisse comme en attente. Stockez un petit patch optimiste et un snapshot de rollback pour confirmer ou annuler juste ce changement sans affecter l'UI non liée.

Comment empêcher les réponses hors d'ordre d'écraser des actions utilisateur plus récentes ?

Attachez un ID de requête à chaque mutation et conservez le dernier ID par élément. À la réception d'une réponse, appliquez-la uniquement si son ID correspond au dernier ID enregistré ; sinon ignorez-la pour empêcher des réponses tardives de ramener l'UI sur une valeur plus ancienne.

Que faire lorsqu'une mise à jour optimiste échoue ?

Pour la plupart des éditions, gardez la valeur de l'utilisateur visible, marquez-la comme non sauvegardée, et affichez une erreur en ligne à l'endroit modifié avec une option de Retry claire. Ne faites un rollback dur que si le changement est réellement impossible (par exemple perte de permission), et expliquez pourquoi.

Dois-je refetcher après une mutation ou patcher le cache localement ?

Refetchez lorsque le changement peut affecter beaucoup d'endroits (totaux, tri, permissions, champs dérivés), car patcher tout correctement est facile à rater. Fusionnez localement quand c'est une mise à jour isolée et que le serveur renvoie l'entité mise à jour, puis acceptez les champs serveur comme les timestamps et les valeurs calculées.

Comment traiter les cas difficiles comme les listes, la pagination et autres ?

Patchs petits et ciblés, IDs temporaires, badges en attente, retries et réconciliation claire : ce sont les ingrédients d'une bonne approche pour les listes. Gardez une map tempId→realId pour remplacer en place et éviter les duplications qui forcent le scroll ou la sélection à se réinitialiser.

Related posts