I am experiencing a problem, which is not very frequent but still occurs on a regular basis, in a web application that makes use of local storage.
The problem is only occurring in iOS/macOS devices, regardless of browser, and the error that is being logged is the following:
TypeError: 'null' is not an object (evaluating 'localStorage.getItem')
Here's how the code works; there is a function (isLocalStorageSupported
) that checks if local storage is supported, and there is another function (getLocalData
) that uses it before trying to get data from the storage.
The error is thrown after the check for local storage completes by returning true, but the next line inexplicably (?) throws when trying to get a key from local storage, as the storage variable at that stage seems to be null.
It's unclear to me how this can happen, hence my question.
// The function that checks availability
const isLocalStorageSupported = (() => {
let localStorageSupported = null;
return () => {
if (localStorageSupported !== null) {
return localStorageSupported;
}
try {
localStorage.setItem('check', 'check');
localStorage.getItem('check');
localStorage.removeItem('check');
localStorageSupported = true;
} catch (error) {
localStorageSupported = false;
console.error('localStorage not supported');
}
return localStorageSupported;
};
})();
const getLocalData = (key) => {
if (isLocalStorageSupported()) { // This check is successful
let data = localStorage.getItem(key); // This line throws a TypeError!
if (!data) {
return;
}
return data;
}
};
localStorage will NOT throw an error if the item doesn't exist. It will return null:
//Purpose of this is to generate a completely random key to prove my point
let randomKey = String.fromCharCode(...crypto.getRandomValues(new Uint8Array(100))).match(/[A-Z]|[a-z]|[0-9]/g).join('');
//Get the value associated with the key (there is none)
console.log(localStorage.getItem(randomKey)); //--> null
If you need to check if a browser supports localStorage, check this way:
let localStorageSupported = 'localStorage' in window;
//this is equal to !!window.localStorage or Boolean(window.localStorage)
if (!localStorageSupported) {
alert('Get a new browser!');
}
Technically : you are caching the result of your check, and return the cached result.
If localStorage
is valid on the first call to isLocalStorageSupported()
, and becomes invalid afterwards, without the page being reloaded, your function would still return true
.
"invalid" could maybe be a change in the state of the built-in localStorage object, or more probably some function mesing with the global localStorage
object.
Extra point, probably not relevant but worth noting :
your code snippet shows an acces to an unqualified variable named localStorage
(e.g : not window.localStorage
), which could be provided or handled by some code in your javascript framework.
The simplest suggestion would be to not cache the result.
To avoid clobbering a possible "check"
key in localStorage
, you may use a more distinctive key name (e.g : "__my_company_check__"
).
You can confirm whether your issue comes from this caching behavior (note: this suggestion still introduces an uncached check at each call site, it will allow you to decide if your issue comes from caching the test value) :
function uncachedCheck() { ... }
function cachedCheck() { ... }
if (uncachedCheck() !== cachedCheck()) {
alert('state of localStorage has changed !')
// feed extra information to Splunk ...
}
I don't have a good suggestion to fix the cached version of your check : perhaps check that localStorage
is still the same object ? (I don't know how different browsers support localStorage
, though)
let localStorageSupported = null;
let checkedValue = undefined;
try {
if ((localStorage === checkedValue) && (localStorageSupported !== null)) {
return localStorageSupported;
}
checkedValue = localStorage;
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With