'Change in localstorage not triggering event listener

I have React component. Initially I set some localStorage in UseEffect. Moreover I add event listener. After clicking on text it changes the localStorage value but event listener does not triggering, why?

import React, { useEffect } from "react";

export default function App() {

  useEffect(() => {
    window.localStorage.setItem("item 1", 'val 1');
    window.addEventListener('storage', () => {
      alert('localstorage changed!')
    })
  }, []);

  const getData = () => {
    localStorage.setItem("item", "val chamged");
  };

  return (
    <div className="App">
      <h1 onClick={getData}>Change localstorage value</h1>

    </div>
  );
}

https://codesandbox.io/s/naughty-engelbart-90tkw



Solution 1:[1]

There are two things wrong.

  1. change onClick={getData()} onClick={getData}
  2. From the doc(https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event). The storage event of the Window interface fires when a storage area (localStorage or sessionStorage) has been modified in the context of another document. Note the last sentence that says it won't be fired in the same document. You can see that if you open https://codesandbox.io/s/spring-browser-89con in 2 tabs in same browser, the alert will start coming.

Solution 2:[2]

The Storage event is triggered when there is a change in the window's storage area.

Note: The storage event is only triggered when a window other than itself makes the changes.

You can see more details and demo: storage Event

The storage event handler will only affect other windows. Whenever something changes in one window inside localStorage all the other windows are notified about it and if any action needs to be taken it can be achieved by a handler function listening to the storage event.

Solution 3:[3]

Try with this onclick handler

import React, { useEffect } from "react";

export default function App() {

  useEffect(() => {
    window.localStorage.setItem("item 1", 'val 1');
    window.addEventListener('storage', () => {
      alert('localstorage changed!')
    })
  }, []);

  const getData = () => {
    localStorage.setItem("item", "val chamged");
  };

  return (
    <div className="App">
      <h1 onClick={()=>getData()}>Change localstorage value</h1>

    </div>
  );
}

Solution 4:[4]

I will suggest a solution. It might be an ugly solution, but it is the only solution that I have found to achieve it from the same window. (for some reasons I need to get the change :) )

I've used the MutationObserver class, for example to get any changes on the dark mode value:

const setTheme = (value) => {
    let htmlClasses = document.querySelector('html').classList
    if (value == true) {
        htmlClasses.add('dark');
    } else {
        htmlClasses.remove('dark');
    }
    window.localStorage.setItem('dark', value)
}

Here I am adding a class each time the toggle button has been activated, then I configure an observer on the html classes like this:

var observer = new MutationObserver(function (mutations) {
  mutations.forEach(function (mutation) {
      console.log(mutation);
      if (mutation.attributeName == "class") {
         // Do what you want here
      }
  });
});
var config = {
  attributes: true
};

observer.observe(document.querySelector('html'), config);

You can see also an other example (more useful for you) that I've found on github here

Solution 5:[5]

To update the localStorage in the same window, you need to dispatch a storage event.
I changed some of the values from the original question to update the same cookie that is initially set.
Don't forget that event listeners need to be removed when the component unmounts and localStorage items should have a same site and secure attribute!

import React, { useEffect } from "react";

export default function App() {

  //Set localStorage item when the component mounts and add storage event listener
  useEffect(() => {
    const alertMessage = () => {
      alert('localStorage changed!');
    }

    window.localStorage.setItem("item", 'val 1', { sameSite: "strict", secure: true });
    window.addEventListener('storage', alertMessage);

    //Remove the event listener when the component unmounts
    return () => {
      window.removeEventListener("storage", alertMessage);
    }
  }, []);

  //Update the localStorage onClick
  const updateData = () => {
    localStorage.setItem("item", "val changed", { sameSite: "strict", secure: true });
    window.dispatchEvent(new Event("storage")); //This is the important part
  };

  return (
    <div className="App">
      <h1 onClick={updateData}>Change localStorage value</h1>
    </div>
  );
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Subin Sebastian
Solution 2 Mohammad Oftadeh
Solution 3 Kesav
Solution 4
Solution 5 Rllyyy