The Bug That Only Showed Up in a Second Tab: Debugging a Multi-Tab localStorage Race Condition
This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry . I was working on InnerHue, a mood-tracking app that stores mood entries in localStorage via a Zustand store. The reported bug: open the app in two tabs, log a mood in each, and entries would randomly go missing or get overwritten. The investigation The Zustand store ( lib/useMoodStore.ts ) kept an in-memory copy of…
This story details a debugging process undertaken for a multi-tab localStorage race condition in the InnerHue app. InnerHue is a mood-tracking application that stores entries in localStorage through a Zustand store. The bug in question was that entries would occasionally vanish or get overwritten when the app was opened in multiple tabs.
After investigation, it was discovered that the Zustand store, responsible for maintaining an in-memory copy of moodHistory, didn't monitor changes originating from other tabs. Hence, each tab maintained its own outdated snapshot of the data. The first tab to write to the localStorage would overwrite the data from the second tab, resulting in the aforementioned race condition.
To rectify this issue, a listener was added to the window's storage event. This listener invoked useMoodStore.persist.rehydrate() whenever the mood-storage key was altered in another tab. This ensured that all open tabs remained synchronized, overcoming the race condition.
Interestingly, while investigating the race condition, another bug was uncovered. This one involved duplicate entries being generated each time the mood page was accessed. Further analysis revealed that this was due to the addMood() function being invoked directly within a useEffect without any safeguards. In development mode, React Strict Mode's double invocation of effects caused the function to be executed twice.
To address this issue, a useRef guard was implemented. This ensured that the function would only be triggered once per genuine page visit. Both bugs originated from the same localStorage write path, hence they were resolved collectively in a single pull request.
The incident serves as a reminder of how bugs that only surface under specific conditions, like multiple tabs open, can be easily overlooked during typical development testing. It also emphasizes the importance of rigorously testing state management code, especially for scenarios involving concurrent access, not just single-tab flows.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.