Почему localStorage по-прежнему приводит к сбою вашего сайта в 2023 году

Почему localStorage по-прежнему приводит к сбою вашего сайта в 2023 году

29 января 2023 г.

Осенью 2022 года я решил перенести один из наших рабочих проектов с приложения Create React на NextJs. Цель этого состояла в том, чтобы повысить производительность нескольких страниц, отделить страницы для мобильных устройств от основного приложения и уменьшить количество сторонних лиц, имеющих доступ. Большинство наших целей были успешно достигнуты, но, по словам нашего уведомителя Airbrake, localStorage всегда что-то ломало. Раньше я не думал, что localStorge будет проблемой, так как я хранил там номера телефонов пользователей и ключи i18n и не мог использовать вместо этого внутренний API.

Итак, мне нужно было, чтобы localStorage работал, так как это был единственный вариант для меня.

Мои проблемы

Одной из проблем, с которыми я столкнулся, была ошибка во время компиляции.

ReferenceError: localStorage is not defined

Да, я определенно был новичком в NextJs и полностью упустил тот факт, что мои страницы в первую очередь выполняют серверную визуализацию. Это означает, что window и localStorage не будут доступны, пока страница не будет загружена на стороне клиента.

import { useEffect } from 'react';

useEffect(() => {
  const phoneNumber = localStorage.getItem('phoneNumber');
}, []);

// and 
const onChange = value => localStorage.setItem('phoneNumber', value);

Решение было довольно простым: поместить все, что с этим связано, в обработчики useEffect и onChange, чтобы убедиться, что это отражается в браузере.

Вторая и третья были моими самыми большими проблемами, потому что мне удалось решить их только после прочтения множества статей, сообщений и страниц документации.

SecurityError: The operation is insecure.

Мы получили эту ошибку только от браузера Safari, поэтому я решил копнуть немного дальше. Нашел интересный факт. Если все файлы cookie в настройках конфиденциальности и безопасности заблокированы пользователем, каждый раз, когда ваш веб-сайт вызывает localStorage или sessionStorage, это будет вызывать ошибку.

// Even calling
console.log(localStorage);
// or
localStorage?.getItem('phoneNumber');

Оборачивать все в try...catch было не лучшим вариантом, потому что это увеличило бы существующую кодовую базу, что является плохой практикой. Следуя фрагменту кода на MDN, я решил написать служебный класс, охватывающий все жизненно важные компоненты, связанные с localStorage.

class AppStorage {
    getItem(key) {
        if (this.storageAvailable()) {
            return window.localStorage?.getItem(key);
        }
        return undefined;
    }

    setItem(key, value) {
        if (this.storageAvailable()) {
            window.localStorage?.setItem(key, value);
        }
    }

    storageAvailable(type = 'localStorage') {
        let storage;
        try {
            storage = window[type];
            const x = '__storage_test__';
            storage.setItem(x, x);
            storage.removeItem(x);
            return true;
        } catch (e) {
            return (
                e instanceof DOMException &&
                // everything except Firefox
                (e.code === 22 ||
                    // Firefox
                    e.code === 1014 ||
                    // test name field too, because code might not be present
                    // everything except Firefox
                    e.name === 'QuotaExceededError' ||
                    // Firefox
                    e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
                // acknowledge QuotaExceededError only if there's something already stored
                storage &&
                storage.length !== 0
            );
        }
    }
}
// and inside our components
const phoneNumber = AppStorage.getItem('phoneNumber');

Выглядит весьма элегантно, не так ли?

У нас есть этот служебный класс в отдельном файле, и мы можем использовать его во всем приложении, применяя стратегию DRY. Кстати, этот служебный класс помог решить и последнюю проблему:

TypeError: Cannot read properties of null (reading 'setItem')

Airbrake прислал нам эту ошибку с Android в новых версиях Chrome. Я до сих пор не понял, чем это вызвано, потому что оператор .? не помог, был определен window.localStorage. Но это было null.

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


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE