0

How can I use localstorage value of benchmarkActiveTab in the dependency array? The value is changing in the localstorage but the useEffect isn't running when the value changes

const [activeTab, setActiveTab] = useState(
  localStorage.getItem("benchmarkActiveTab") || 0
)

useEffect(() => {
  async function getBenchmarkTimes() {
    // this function doesn't run
  }
  getBenchmarkTimes()
}, [activeTab])

I tried to make the useEffect run when the localStorage.getItem('benchmarkActiveTab') changes, but the useEffect doesn't run.

winwin
  • 57
  • 6
  • 2
    Where are you setting this value in `localStorage`? – Unmitigated Mar 28 '23 at 14:38
  • @Unmitigated In a different component – Andrew McMahon Mar 28 '23 at 14:40
  • 1
    https://usehooks.com/useLocalStorage/ – Daniel A. White Mar 28 '23 at 14:46
  • 2
    localStorage is a round-about way share data between components, there are many more effective strategies to be employed before resorting to it, from passing state down, shared state in a parent, or using a context. As for your dependency, the state initializer only runs on first mount, so `activeTab` will never change based on what code you've shown. – pilchard Mar 28 '23 at 14:56
  • @DanielA.White it looks like that hook has the same limitations, it won't update on localStorage change, just on remount of the component. However [this hook](https://usehooks-ts.com/react-hook/use-local-storage) includes a listener for change. – pilchard Mar 28 '23 at 14:59
  • duplicate [How to listen to localstorage value changes in react?](https://stackoverflow.com/questions/56660153/how-to-listen-to-localstorage-value-changes-in-react) – pilchard Mar 28 '23 at 15:03

2 Answers2

1

You need to listen to storage event

const [activeTab, setActiveTab] = useState(
  localStorage.getItem("benchmarkActiveTab") || 0
)

useEffect(() => {
  const OnStorageEvent = async () => {
    const value = localStorage.getItem('benchmarkActiveTab') || 0

    if (activeTab !== value) {
      // value change
      setActiveTab(value)

      // Run getBenchmarkTimes here
      // use value instead of activeTab here because react update after re render
    }
  }

  addEventListener("storage", OnStorageEvent)

  return () => removeEventListener("storage", OnStorageEvent)
}, [activeTab])
kennarddh
  • 2,186
  • 2
  • 6
  • 21
0

In your code, the state activeTab is only set once during the initial render. It never changes again. So the effect depends on it will never run again either.

You can use useSyncExternalStore hook. This hook accept two kind of params: subscribe and getSnapshot. Every time subscribe call the callback, React calls getSnapshot and gets a fresh snapshot. If the snapshot changes (when result from Object.is is false), React rerender the component.

function useLocalStorageValue(key, interval = 100) {
  // run callback intervally
  const subscribe = (callback) => {
    let enabled = true;
    const fn = () =>
      setTimeout(() => {
        callback();
        if (enabled) fn();
      }, interval);
    fn();
    return () => {
      enabled = false;
    };
  };

  // get value from localstorage
  const getSnapShot = () => {
    return localStorage.getItem(key);
  };

  return useSyncExternalStore(subscribe, getSnapShot, getSnapShot);
}

See full example here.

winwin
  • 57
  • 6