Back

Как сохранять состояние формы в браузере

Как сохранять состояние формы в браузере

Вы тратите десять минут на заполнение многошаговой анкеты при устройстве на работу. Случайно нажимаете кнопку «Назад». Всё пропало.

Это один из самых раздражающих UX-провалов в веб-разработке, и его полностью можно предотвратить. Вот как сохранить состояние формы в браузере, используя подходящий механизм хранения для вашей ситуации.

Ключевые выводы

  • Одностраничные приложения часто теряют данные форм при навигации, потому что DOM может пересоздаваться с нуля, а не восстанавливаться из кеша.
  • Выбирайте хранилище в зависимости от требуемого срока жизни данных: localStorage — для долгосрочных черновиков, sessionStorage — для сессий в одной вкладке, IndexedDB — для больших или структурированных данных.
  • Базовый паттерн автосохранения прост: применить debounce к событиям ввода, сохранить в хранилище, восстановить при монтировании и очистить после успешной отправки.
  • Никогда не храните пароли, токены или платёжные данные в Web Storage — они уязвимы для XSS-атак.
  • Всегда оборачивайте запись в хранилище в try/catch, чтобы корректно обрабатывать QuotaExceededError и партиционированное хранилище.

Почему данные форм исчезают в современных приложениях

Традиционным страницам с серверным рендерингом здесь немного проще. Браузеры часто восстанавливают значения форм при навигации назад, потому что сама страница кеширована. Одностраничным приложениям такое преимущество доступно не всегда. Когда ваш JavaScript заново рендерит форму с нуля, у браузера может не быть кешированного DOM для восстановления, поэтому поля возвращаются пустыми.

Решение — сохранять состояние формы в браузерное хранилище самостоятельно и восстанавливать его при монтировании формы.

Выбор подходящего механизма хранения

Не каждая задача персистентности требует одного и того же решения. Вот практическое сравнение:

МетодСохраняется доИзолирован по вкладкамЛимит размераЛучше всего для
localStorageРучной очисткиНет~5–10 МБДолгосрочные черновики
sessionStorageЗакрытия вкладкиДа~5–10 МБФормы в рамках одной сессии
IndexedDBРучной очисткиНетЗависит от браузераБольшие или структурированные данные
History API stateЗаписи в истории навигацииДаНебольшие объектыНавигация назад/вперёд в SPA

Формы на localStorage хорошо подходят для черновиков, которые должны переживать перезапуск браузера, — например, для редактора блога или длинной формы регистрации. Формы на sessionStorage лучше, когда данные должны переживать только обновление страницы в той же вкладке, но не передаваться между вкладками. Черновики форм в IndexedDB имеют смысл, когда вы сохраняете насыщенный контент, метаданные файлов или сложные вложенные объекты, которые было бы неудобно хранить в виде JSON-строк.

И localStorage, и sessionStorage синхронны и работают со строками, то есть каждое чтение и запись блокируют главный поток и требуют JSON.stringify/JSON.parse. IndexedDB асинхронна и работает со структурированными данными нативно, что делает её более подходящей для всего, что выходит за рамки простых пар «ключ-значение». Все три варианта хорошо поддерживаются в современных браузерах.

Реализация автосохранения с localStorage

Базовый паттерн прост: сохранять при вводе, восстанавливать при загрузке, очищать после успешной отправки.

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

Debounce вызова сохранения (здесь 500 мс) предотвращает излишнюю нагрузку на хранилище при каждом нажатии клавиши. Всегда оборачивайте setItem в try/catch — браузеры могут выбрасывать QuotaExceededError, особенно в условиях ограниченного объёма хранилища или при партиционировании стороннего хранилища. Оборачивать JSON.parse в try/catch также разумно, так как повреждённый черновик иначе выбросит исключение и сломает шаг восстановления.

Что не следует хранить

Никогда не сохраняйте пароли, номера платёжных карт, токены аутентификации или любые конфиденциальные персональные данные в localStorage или sessionStorage. Эти API доступны любому JavaScript-коду, выполняемому на странице, что делает их уязвимыми для XSS-атак. Если ваша форма собирает конфиденциальные поля, полностью исключите их из логики черновика.

Также стоит знать: во встроенных или сторонних контекстах браузеры всё чаще партиционируют или ограничивают доступ к Web Storage. Не предполагайте, что хранилище всегда доступно — проверяйте его наличие и корректно обрабатывайте отказы.

Очистка черновиков и крайние случаи

Всегда удаляйте черновик после успешной отправки формы. Устаревшие черновики, которые неожиданно появляются вновь, сбивают пользователей с толку. Если структура вашей формы со временем меняется, рассмотрите возможность версионирования ключа хранилища (например, contact_form_draft_v2), чтобы старые черновики не вызывали тихих ошибок восстановления.

Заключение

Сохранение состояния формы в браузере не требует ни библиотеки, ни бэкенда. Небольшого объёма продуманного JavaScript — сохранения с debounce, безопасного восстановления и очистки при отправке — достаточно, чтобы предотвратить потерю данных и сделать ваши формы заметно надёжнее.

Часто задаваемые вопросы

Используйте localStorage, когда хотите, чтобы черновик пережил перезапуск браузера, например для длинных форм, редакторов блогов или многошаговых анкет, к которым пользователи могут вернуться через несколько дней. Используйте sessionStorage, когда черновик должен пережить лишь случайное обновление страницы в той же вкладке и исчезнуть при её закрытии. У них одинаковый API, поэтому переключение между ними — это изменение в одну строку.

Файловые поля нельзя восстановить программно по соображениям безопасности. Браузер не позволит вам установить значение для input типа file. Если пользователи загружают файлы, сохраняйте метаданные файла или сразу загружайте на сервер и сохраняйте полученный идентификатор. Для крупных файлов, хранящихся на клиенте, используйте IndexedDB, чтобы хранить объект File или Blob напрямую до момента отправки.

Для типичных форм — нет. Записи в localStorage быстры для небольших данных, а debounce событий ввода примерно до 500 миллисекунд делает записи нечастыми. Проблемы появляются, когда черновики становятся большими или сохранение выполняется при каждом нажатии клавиши без debounce, так как каждая запись блокирует главный поток. Для больших или структурированных данных переходите на IndexedDB — она асинхронна и не блокирует поток.

Слушайте событие storage на объекте window. Оно срабатывает в других вкладках всякий раз, когда localStorage изменяется в одной из вкладок, и передаёт ключ и новое значение. После этого вы можете соответствующим образом обновить поля формы во вкладках-слушателях. Обратите внимание, что событие storage не срабатывает во вкладке, которая внесла изменение, — только в других вкладках, открытых на том же источнике (origin).

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