Listen for changes with localStorage on the same window
Asked Answered
C

5

29

I want to listen for changes that are happening in the localStorage API on the same page (Not in multiple tabs like the spec says).

I am currently using this code:

var storageHandler = function () {
    alert('storage event 1');
  };

  window.addEventListener("storage", storageHandler, false);

localStorage.setItem('foo', 'bar');

Does anyone know a vanilla JavaScript way to listen to events on localStorage on one page (no jQuery)

Crystallite answered 17/11, 2014 at 13:46 Comment(0)
I
34

Since JS is dynamical language just rewrite original functions.

var originalSetItem = localStorage.setItem; 
localStorage.setItem = function(){
    document.createEvent('Event').initEvent('itemInserted', true, true);
    originalSetItem.apply(this, arguments);
}
Infant answered 17/11, 2014 at 14:6 Comment(7)
Hurray for JavaScript and rewriting original functions!Crystallite
@f.lorenzo, rigtht but maybe it's a good point to stay and think why the W3C collaborators decided to made events available only for different pages.Infant
Problems arise if the same event propagates to all windows, including the setting window. Here you have introduced a distinct event type which avoids lots of problemsDecoction
What if you are setting the value in one component and want to listen from a different one?Felske
Note that this will not work if the localStorage is set as array, as localStorage["foo"]="bar", or as localStorage.foo="bar"Systemic
You will need to combine it with this answer somehow https://mcmap.net/q/501242/-override-dot-notation-for-localstorageSystemic
Does it work when changing localStorage via browser dev tools? If yes it would be worth mentioning itEclectic
T
39

Updated above answer, as document.createEvent now is part of an old, deprecated API.

const originalSetItem = localStorage.setItem;

localStorage.setItem = function(key, value) {
  const event = new Event('itemInserted');

  event.value = value; // Optional..
  event.key = key; // Optional..

  document.dispatchEvent(event);

  originalSetItem.apply(this, arguments);
};

const localStorageSetHandler = function(e) {
  alert('localStorage.set("' + e.key + '", "' + e.value + '") was called');
};

document.addEventListener("itemInserted", localStorageSetHandler, false);

localStorage.setItem('foo', 'bar'); // Pops an alert

https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events

Tussah answered 26/4, 2017 at 12:25 Comment(9)
This strategy doesn't appear to work in Firefox. At least not in version 61.0.1. Maybe you're not allowed to overwrite native functions in Firefox? Hard to say because I can't inspect the function in the Firefox console.Eggleston
Hmm that's unfortunate. No idea why. But you could always write a custom function and call that instead of using and modifying localStorage.setItem directly.Tussah
That is what I ended up doing.Eggleston
@Tussah In the storageHandler function, is there a way to pull the value that was updated from the e parameter being passed in?Tetracaine
Is it possible to pass the value that is being added to the localStorage as you dispatch the event then get it from the listener?Mcgaw
@BrandonFranklin - Yes, you can assign properties to the Event object you created, I've added it to the example. I've chosen "value" and "key" as property names, but you can chose any names you prefer.Tussah
@Mcgaw - Yup! I've updated the example to show how you can add props to the Event object.Tussah
@Tussah Thanks. Much AppreciatedMcgaw
thanks for this. i'll just add that, though the Event() type seems to work just fine for attaching extra information, there's also a Customevent() type that i believe was built with appending additional event properties in mind.Lagging
I
34

Since JS is dynamical language just rewrite original functions.

var originalSetItem = localStorage.setItem; 
localStorage.setItem = function(){
    document.createEvent('Event').initEvent('itemInserted', true, true);
    originalSetItem.apply(this, arguments);
}
Infant answered 17/11, 2014 at 14:6 Comment(7)
Hurray for JavaScript and rewriting original functions!Crystallite
@f.lorenzo, rigtht but maybe it's a good point to stay and think why the W3C collaborators decided to made events available only for different pages.Infant
Problems arise if the same event propagates to all windows, including the setting window. Here you have introduced a distinct event type which avoids lots of problemsDecoction
What if you are setting the value in one component and want to listen from a different one?Felske
Note that this will not work if the localStorage is set as array, as localStorage["foo"]="bar", or as localStorage.foo="bar"Systemic
You will need to combine it with this answer somehow https://mcmap.net/q/501242/-override-dot-notation-for-localstorageSystemic
Does it work when changing localStorage via browser dev tools? If yes it would be worth mentioning itEclectic
O
12

The answers to this question didn't work for me. I got an Uncaught TypeError: Illegal invocation so I wrote my own code which works in most environments. It uses Proxy which is quite a bit safer.

Storage.prototype.setItem = new Proxy(Storage.prototype.setItem, {
    apply(target, thisArg, argumentList) {
        const event = new CustomEvent('localstorage', {
            detail: {
                key: argumentList[0],
                oldValue: thisArg.getItem(argumentList[0]),
                newValue: argumentList[1],
            },
        });
        window.dispatchEvent(event);
        return Reflect.apply(target, thisArg, argumentList);
    },
});

Storage.prototype.removeItem = new Proxy(Storage.prototype.removeItem, {
    apply(target, thisArg, argumentList) {
        const event = new CustomEvent('localstorage', {
            detail: {
                key: argumentList[0],
            },
        });
        window.dispatchEvent(event);
        return Reflect.apply(target, thisArg, argumentList);
    },
});

Storage.prototype.clear = new Proxy(Storage.prototype.clear, {
    apply(target, thisArg, argumentList) {
        const event = new CustomEvent('localstorage', {
            detail: {
                key: '__all__',
            },
        });
        window.dispatchEvent(event);
        return Reflect.apply(target, thisArg, argumentList);
    },
});
Oxidase answered 29/9, 2021 at 17:22 Comment(2)
What's Reflect?Osage
Reflect is an object that is used with Proxy, you use it to intercept some operation. You can read more about it here developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… and here javascripttutorial.net/es6/javascript-reflection I am not an expert on the subject, I found this in a snippet and read some docs to implement it here.Oxidase
H
4

Saving data in a new local storage item with an specific Key name, and dispatch an event (first file)

localStorage.setItem("newDataKey", "data");
window.dispatchEvent(new Event("NewDataEvent"));

Hearing for changes on that particular Key name and console log his content as demonstration. (second file)

window.addEventListener("NewDataEvent", function() {
    console.log(localStorage.getItem("newDataKey"));
});
Hollis answered 12/8, 2023 at 14:25 Comment(8)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Infringe
Actually is pretty clear xD, may be bots here?Hollis
What does this answer add to the existing answers? Other answers already showed how to use events.Shielashield
Is a usefull boilerplate to try the event dispatcher for the people that is here learning, instead of your answer that is more for a gossip program than for a programming page.Hollis
Others already provided the same boilerplate, so again, what does this answer add to the existing answers?Shielashield
And what "my answer" are you referring to? I didn't post an answer here.Shielashield
Are you bored gre_gor?Hollis
I found this answer helpful because it was the simplest and hence the easiest to understand, implement and maintain. Thanks!Disject
M
3

Fixed. It works in Chrome and Firefox too. Solution here

var buttonTrigger = document.getElementById('triggerEvent');

buttonTrigger.addEventListener('click', function() {
  window.localStorage.setItem('k1', document.getElementById('storageValue').value);
  window.dispatchEvent(evt);

});

window.addEventListener('storage', function(e) {
  document.getElementById('displayValue').innerHTML = localStorage.getItem('k1');
}, false);


var evt = document.createEvent('StorageEvent'); 

evt.initStorageEvent('storage', false, false, 'k1', 'oldValue', 'newValue',null, window.localStorage); 
      
window.dispatchEvent(evt);
Muzhik answered 18/2, 2022 at 17:33 Comment(1)
Maybe a bit offtopic, but why was it changed so that by default in same window it doesn't trigger the event listener? Is there cause for concern when changing this behaviour? InitStorageEvent seems to be deprecated now developer.mozilla.org/en-US/docs/Web/API/StorageEvent/…Luby

© 2022 - 2024 — McMap. All rights reserved.