I have a custom hook to manage localStorage like below:
import { useEffect, useState } from 'react'
export default function useLocalStorage(key, initValue) {
const [state, setState] = useState(() => {
const value = localStorage.getItem(key);
if (value !== null) {
return JSON.parse(value);
}
localStorage.setItem(key, JSON.stringify(initValue));
return initValue;
})
useEffect(() => {
localStorage.setItem(key, state);
}, [key, state])
return [state, setState];
}
This custom hook is declared to use the same localstorage key for two or more components in one screen. e,g.
const [state, setState] = useLocalStorage('key', false);
At this time, since components using useLocalStorage want to work according to the state change of localStorage, we try to use the state returned by useLocalStorage as the second parameter of useEffect.
useEffect(() => {
foobar()
}, [state]);
I expected the foobar() function in useEffect to work when the "state" changes in all components that declared this useEffect. However, foobar() only worked for one component out of several components using this same useEffect.Why this is happening? How do I get it to work the way I want it to?
You should set up an event listener for storage changes in order to have the updated value. See the code below and the note about storage event:
import { useEffect, useState } from 'react'
export default function useLocalStorage(key, initValue) {
const [state, setState] = useState(() => {
const value = localStorage.getItem(key);
if (value !== null) {
return JSON.parse(value);
}
localStorage.setItem(key, JSON.stringify(initValue));
window.dispatchEvent(new Event("storage"));
return initValue;
});
useEffect(() => {
localStorage.setItem(key, state);
window.dispatchEvent(new Event("storage"));
}, [key, state]);
useEffect(() => {
const listenStorageChange = () => {
setState(() => {
const value = localStorage.getItem(key);
if (value !== null) {
return JSON.parse(value);
}
localStorage.setItem(key, JSON.stringify(initValue));
window.dispatchEvent(new Event("storage"));
return initValue;
});
};
window.addEventListener("storage", listenStorageChange);
return () => window.removeEventListener("storage", listenStorageChange);
}, []);
return [state, setState];
}
You might wonder why this dispatchEvent is used. Well here is what MDN says about storage event:
The
storageevent of theWindowinterface fires when a storage area (localStorage) has been modified in the context ofanother document.
This dispatchEvent is needed as we need to listen to changes in the same document as well.