Back

Como Persistir o Estado de Formulários no Navegador

Como Persistir o Estado de Formulários no Navegador

Você passa dez minutos preenchendo uma candidatura de emprego em várias etapas. Por acidente, clica no botão “voltar”. Tudo se perdeu.

Esta é uma das falhas de UX mais frustrantes no desenvolvimento web e é totalmente evitável. Veja como persistir o estado de formulários no navegador utilizando o mecanismo de armazenamento adequado para a sua situação.

Principais Conclusões

  • Aplicações de página única (SPAs) frequentemente perdem dados de formulário durante a navegação porque o DOM pode ser renderizado novamente do zero, em vez de restaurado a partir do cache.
  • Escolha o armazenamento com base nas necessidades de duração: localStorage para rascunhos de longo prazo, sessionStorage para sessões de uma única aba e IndexedDB para dados grandes ou estruturados.
  • O padrão central de autosave é simples: aplicar debounce nos eventos de input, salvar no armazenamento, restaurar na montagem e limpar após o envio bem-sucedido.
  • Nunca armazene senhas, tokens ou detalhes de pagamento no Web Storage — eles são vulneráveis a ataques XSS.
  • Sempre envolva as escritas de armazenamento em try/catch para lidar com QuotaExceededError e armazenamento particionado de forma elegante.

Por Que os Dados de Formulários Desaparecem em Aplicações Modernas

Páginas tradicionais renderizadas pelo servidor têm uma pequena vantagem aqui. Os navegadores frequentemente restauram os valores dos formulários ao navegar para trás, porque a própria página é armazenada em cache. Aplicações de página única nem sempre têm esse benefício. Quando seu JavaScript renderiza novamente um formulário do zero, o navegador pode não ter um DOM em cache para restaurar, fazendo com que os campos voltem vazios.

A solução é salvar você mesmo o estado do formulário no armazenamento do navegador e restaurá-lo quando o formulário for montado.

Escolhendo o Mecanismo de Armazenamento Adequado

Nem todo problema de persistência exige a mesma solução. Aqui está uma comparação prática:

MétodoPersiste AtéIsolado por AbaLimite de TamanhoMelhor Para
localStorageLimpeza manualNão~5–10 MBRascunhos de longo prazo
sessionStorageFechamento da abaSim~5–10 MBFormulários de sessão única
IndexedDBLimpeza manualNãoDepende do navegadorDados grandes ou estruturados
History API stateEntrada de navegaçãoSimObjetos pequenosNavegação back/forward em SPAs

localStorage funciona bem para rascunhos que você quer que sobrevivam a reinicializações do navegador, como um editor de posts de blog ou um formulário longo de registro. Formulários com sessionStorage são melhores quando você só precisa que os dados sobrevivam a uma atualização dentro da mesma aba, e não entre abas. Rascunhos de formulários com IndexedDB fazem sentido quando você está armazenando conteúdo rico, metadados de arquivos ou objetos aninhados complexos que seriam difíceis de manejar como strings JSON.

Tanto localStorage quanto sessionStorage são síncronos e baseados em strings, o que significa que toda leitura e escrita bloqueia a thread principal e requer JSON.stringify/JSON.parse. O IndexedDB é assíncrono e lida com dados estruturados nativamente, tornando-o uma melhor opção para qualquer coisa além de pares simples de chave-valor. Os três têm bom suporte em navegadores modernos.

Implementando Autosave com localStorage

O padrão central é direto: salvar no input, restaurar no carregamento, limpar após o envio bem-sucedido.

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);
});

Aplicar debounce na chamada de salvamento (500ms aqui) evita sobrecarregar o armazenamento a cada tecla pressionada. Sempre envolva setItem em um try/catch — os navegadores podem lançar (e lançam) QuotaExceededError, especialmente em ambientes com pouco armazenamento ou quando o armazenamento de terceiros está particionado. Também é prudente envolver o JSON.parse em um try/catch, já que um rascunho corrompido lançaria uma exceção e quebraria a etapa de restauração.

O Que Não Armazenar

Nunca persista senhas, números de cartões de pagamento, tokens de autenticação ou quaisquer dados pessoais sensíveis em localStorage ou sessionStorage. Essas APIs são acessíveis a qualquer JavaScript em execução na página, tornando-as vulneráveis a ataques XSS. Se seu formulário coleta campos sensíveis, exclua-os completamente da sua lógica de rascunho.

Também vale saber: em contextos embarcados ou de terceiros, os navegadores cada vez mais particionam ou restringem o acesso ao Web Storage. Não presuma que o armazenamento esteja sempre disponível — verifique e falhe de forma elegante.

Limpeza de Rascunhos e Casos Especiais

Sempre exclua o rascunho após um envio bem-sucedido do formulário. Rascunhos desatualizados que reaparecem inesperadamente confundem os usuários. Se a estrutura do seu formulário mudar ao longo do tempo, considere versionar sua chave de armazenamento (por exemplo, contact_form_draft_v2) para que rascunhos antigos não causem erros silenciosos de restauração.

Conclusão

A persistência de formulários no navegador não exige uma biblioteca nem um backend. Uma pequena quantidade de JavaScript deliberado — salvamentos com debounce, restaurações seguras e limpeza no envio — é suficiente para evitar a perda de dados e fazer seus formulários parecerem visivelmente mais confiáveis.

Perguntas Frequentes

Use localStorage quando quiser que o rascunho sobreviva a reinicializações do navegador, como formulários longos, editores de blog ou aplicações de várias etapas às quais os usuários podem retornar dias depois. Use sessionStorage quando o rascunho só precisa sobreviver a uma atualização acidental dentro da mesma aba e deve desaparecer quando a aba for fechada. Ambos compartilham a mesma API, então alternar entre eles é uma mudança de uma única linha.

Inputs de arquivo não podem ser restaurados programaticamente por razões de segurança. O navegador não permitirá que você defina o valor de um input de arquivo. Se os usuários fazem upload de arquivos, armazene os metadados do arquivo ou faça o upload imediatamente para o servidor e persista o ID de referência resultante. Para arquivos maiores mantidos no lado do cliente, use IndexedDB para armazenar o objeto File ou Blob diretamente até o envio.

Para formulários típicos, não. As escritas no localStorage são rápidas para pequenas cargas úteis, e aplicar debounce nos eventos de input para cerca de 500 milissegundos mantém as escritas pouco frequentes. Os problemas aparecem quando os rascunhos crescem muito ou quando os salvamentos ocorrem a cada tecla pressionada sem debounce, já que cada escrita bloqueia a thread principal. Para dados grandes ou estruturados, mude para o IndexedDB, que é assíncrono e não bloqueante.

Escute o evento storage no objeto window. Ele é disparado em outras abas sempre que o localStorage muda em uma aba, fornecendo a chave e o novo valor. Você pode então atualizar os campos do formulário nas abas que estão escutando. Observe que o evento storage não é disparado na aba que fez a alteração, apenas nas outras abas que estão visualizando a mesma origem.

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