Back

Comment persister l'état d'un formulaire dans le navigateur

Comment persister l'état d'un formulaire dans le navigateur

Vous passez dix minutes à remplir une candidature en plusieurs étapes. Vous appuyez accidentellement sur le bouton retour. Tout est perdu.

C’est l’un des échecs UX les plus frustrants du développement web, et il est entièrement évitable. Voici comment persister l’état d’un formulaire dans le navigateur en utilisant le mécanisme de stockage adapté à votre situation.

Points clés à retenir

  • Les applications monopages perdent souvent les données de formulaire lors de la navigation, car le DOM peut être recréé entièrement plutôt que restauré depuis le cache.
  • Choisissez votre stockage selon la durée de vie nécessaire : localStorage pour les brouillons à long terme, sessionStorage pour les sessions sur un seul onglet, et IndexedDB pour les données volumineuses ou structurées.
  • Le modèle de base de l’autosave est simple : debouncer les événements de saisie, sauvegarder dans le stockage, restaurer au montage, et nettoyer après soumission réussie.
  • Ne stockez jamais de mots de passe, jetons ou informations de paiement dans le Web Storage — ils sont vulnérables aux attaques XSS.
  • Encapsulez toujours les écritures de stockage dans un try/catch pour gérer proprement QuotaExceededError et le stockage partitionné.

Pourquoi les données de formulaire disparaissent dans les applications modernes

Les pages traditionnelles rendues côté serveur bénéficient ici d’un léger sursis. Les navigateurs restaurent souvent les valeurs des formulaires lors de la navigation arrière, car la page elle-même est mise en cache. Les applications monopages ne bénéficient pas toujours de cet avantage. Quand votre JavaScript recrée un formulaire à partir de zéro, le navigateur n’a parfois aucun DOM en cache à restaurer, et les champs reviennent vides.

La solution est de sauvegarder vous-même l’état du formulaire dans le stockage du navigateur, et de le restaurer lorsque le formulaire est monté.

Choisir le bon mécanisme de stockage

Tous les problèmes de persistance n’appellent pas la même solution. Voici une comparaison pratique :

MéthodePersiste jusqu’àIsolé par ongletLimite de tailleIdéal pour
localStorageEffacement manuelNon~5–10 MoBrouillons à long terme
sessionStorageFermeture de l’ongletOui~5–10 MoFormulaires sur session unique
IndexedDBEffacement manuelNonVariable selon navigateurDonnées volumineuses ou structurées
État de l’API HistoryEntrée de navigationOuiPetits objetsNavigation arrière/avant en SPA

localStorage convient bien aux brouillons que vous voulez voir survivre aux redémarrages du navigateur, comme un éditeur d’articles de blog ou un long formulaire d’inscription. Les formulaires en sessionStorage sont préférables lorsque vous n’avez besoin de conserver les données qu’au cours d’une actualisation dans le même onglet, et non entre plusieurs onglets. Les brouillons de formulaire en IndexedDB ont du sens lorsque vous stockez du contenu riche, des métadonnées de fichiers ou des objets imbriqués complexes qui seraient peu maniables sous forme de chaînes JSON.

localStorage et sessionStorage sont tous deux synchrones et basés sur des chaînes, ce qui signifie que chaque lecture et écriture bloque le thread principal et nécessite JSON.stringify/JSON.parse. IndexedDB est asynchrone et gère nativement les données structurées, ce qui le rend plus adapté à tout ce qui dépasse les simples paires clé-valeur. Les trois sont bien pris en charge par les navigateurs modernes.

Implémenter l’autosave avec localStorage

Le modèle de base est simple : sauvegarder à la saisie, restaurer au chargement, nettoyer après soumission réussie.

const DRAFT_KEY = 'contact_form_draft';
const form = document.querySelector('#contact-form');

// Autosave with debounce
let saveTimer;
form.addEventListener('input', (e) => {
  clearTimeout(saveTimer);
  saveTimer = setTimeout(() => {
    const formData = Object.fromEntries(new FormData(e.currentTarget));
    try {
      localStorage.setItem(DRAFT_KEY, JSON.stringify(formData));
    } catch (err) {
      if (err.name === 'QuotaExceededError') {
        console.warn('Storage full, draft not saved');
      }
    }
  }, 500);
});

// Restore on load
window.addEventListener('DOMContentLoaded', () => {
  const saved = localStorage.getItem(DRAFT_KEY);
  if (!saved) return;
  try {
    const draft = JSON.parse(saved);
    Object.entries(draft).forEach(([name, value]) => {
      const field = form.querySelector(`[name="${name}"]`);
      if (field) field.value = value;
    });
  } catch {
    localStorage.removeItem(DRAFT_KEY);
  }
});

// Clear after successful submit
form.addEventListener('submit', () => {
  localStorage.removeItem(DRAFT_KEY);
});

Le debounce de l’appel de sauvegarde (500 ms ici) évite de solliciter le stockage à chaque frappe. Encapsulez toujours setItem dans un try/catch — les navigateurs peuvent lever, et lèvent effectivement, QuotaExceededError, particulièrement dans les environnements à faible capacité de stockage ou lorsque le stockage tiers est partitionné. Encapsuler JSON.parse dans un try/catch est également une bonne pratique, puisqu’un brouillon corrompu provoquerait sinon une exception et casserait l’étape de restauration.

Ce qu’il ne faut pas stocker

Ne persistez jamais de mots de passe, numéros de carte bancaire, jetons d’authentification ou autres données personnelles sensibles dans localStorage ou sessionStorage. Ces API sont accessibles à tout JavaScript exécuté sur la page, ce qui les rend vulnérables aux attaques XSS. Si votre formulaire collecte des champs sensibles, excluez-les entièrement de votre logique de brouillon.

À savoir également : dans les contextes embarqués ou tiers, les navigateurs partitionnent ou restreignent de plus en plus l’accès au Web Storage. Ne supposez pas que le stockage est toujours disponible — vérifiez-le et gérez les échecs proprement.

Effacer les brouillons et gérer les cas limites

Supprimez toujours le brouillon après une soumission réussie du formulaire. Des brouillons obsolètes qui réapparaissent de façon inattendue déroutent les utilisateurs. Si la structure de votre formulaire évolue au fil du temps, envisagez de versionner votre clé de stockage (par exemple, contact_form_draft_v2) afin que les anciens brouillons ne provoquent pas d’erreurs silencieuses lors de la restauration.

Conclusion

La persistance des formulaires dans le navigateur ne nécessite ni bibliothèque ni backend. Un peu de JavaScript bien pensé — sauvegardes debouncées, restaurations sécurisées et nettoyage à la soumission — suffit à éviter la perte de données et à rendre vos formulaires sensiblement plus fiables.

FAQ

Utilisez localStorage lorsque vous souhaitez que le brouillon survive aux redémarrages du navigateur, comme pour les longs formulaires, les éditeurs de blog ou les candidatures en plusieurs étapes auxquelles les utilisateurs peuvent revenir des jours plus tard. Utilisez sessionStorage lorsque le brouillon doit seulement survivre à une actualisation accidentelle dans le même onglet et disparaître à la fermeture de l'onglet. Les deux partagent la même API, donc passer de l'un à l'autre ne demande qu'une modification d'une ligne.

Les champs de type fichier ne peuvent pas être restaurés par programmation pour des raisons de sécurité. Le navigateur ne vous laissera pas définir la valeur d'un input file. Si les utilisateurs téléversent des fichiers, stockez les métadonnées du fichier ou téléversez-le immédiatement sur le serveur et persistez l'identifiant de référence obtenu. Pour des fichiers plus volumineux conservés côté client, utilisez IndexedDB pour stocker directement l'objet File ou Blob jusqu'à la soumission.

Pour des formulaires typiques, non. Les écritures dans localStorage sont rapides pour de petites charges utiles, et le debounce des événements de saisie à environ 500 millisecondes maintient les écritures peu fréquentes. Les problèmes apparaissent lorsque les brouillons deviennent volumineux ou que les sauvegardes s'exécutent à chaque frappe sans debounce, car chaque écriture bloque le thread principal. Pour des données volumineuses ou structurées, passez à IndexedDB, qui est asynchrone et non bloquant.

Écoutez l'événement storage sur l'objet window. Il se déclenche dans les autres onglets chaque fois que localStorage change dans un onglet, vous fournissant la clé et la nouvelle valeur. Vous pouvez alors mettre à jour les champs du formulaire dans les onglets en écoute en conséquence. Notez que l'événement storage ne se déclenche pas dans l'onglet qui a effectué la modification, mais uniquement dans les autres onglets affichant la même origine.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay