
Seamlessly Sync Variables Across Tabs and Reloads
Ever found yourself repeatedly writing state management logic for localStorage or sessionStorage—only to discover it’s a bit more complicated than just calling getItem() or setItem() in React? You’re not alone! Tracking changes in these storages across multiple components or tabs can get messy quickly. Enter React’s useSyncExternalStore hook—a relatively new addition (as of React 18) that lets you build stable, consistent, and reactive custom storage hooks.
This blog post demonstrates how you can leverage useSyncExternalStore to manage data in both sessionStorage and localStorage with surprising ease. We’ll break down how it works, why it’s beneficial, and share a demo that showcases how you can persist React variables through tabs, sessions, and reloads.
useSyncExternalStore is a hook that allows you to build custom subscriptions to external data sources—like local or session storage—and keep React states in sync only when these stores truly update.We’ve built a small Vite + React app that illustrates exactly how these hooks work in practice. The project uses:
useLocalStorage to store user theme information (light/dark mode).useSessionStorage to handle short-lived, session-scoped data like a selected account type.You can find the project here: Demo Repo Link
Feel free to clone the repo, run it locally, and see how each storage hook performs when switching tabs or refreshing pages. This hands-on experience will help solidify your understanding of how useSyncExternalStore can simplify state persistence in your React applications.
useSyncExternalStoreuseSyncExternalStore is designed to integrate an external data source with React’s concurrency features. It guarantees React always has the latest snapshot of that data, plus a subscription mechanism for updates. The hook involves three major parts:
subscribe: Defines how React should listen for changes in the external store. Often, you’ll attach an event listener (like storage) and return a cleanup function that removes it.getSnapshot: Returns the latest value from the store. React calls this to fetch current data during renders.useSyncExternalStore Useful?Before diving into custom hooks, let’s clarify why you might want to store something in sessionStorage vs. localStorage:
Why treat them similarly? In practice, reading and writing to each is nearly identical; you just switch window.localStorage for window.sessionStorage. That’s why you can build a single “storage pattern” and adapt it seamlessly for either type.
We introduce a useBrowserStorage hook that consolidates all the logic for reading from and writing to web storage. Then we wrap it with two specialized hooks:
useLocalStorageuseSessionStorageThis abstraction allows you to manage both storage types with consistent logic, avoiding code duplication and ensuring reliable state synchronization across your application.
useBrowserStorage WorksThe useBrowserStorage hook serves as the foundation for managing data in either localStorage or sessionStorage. It can be found in the project at src/shared/hooks. Here’s how its key components function:
getSnapshot function synchronously returns the current value from the browser storage. It’s essential for useSyncExternalStore, allowing React to retrieve the freshest data during renders.getSnapshot runs, the hook parses the stored JSON. If the parsed data differs from the cached ref, the ref is updated. This ensures the snapshot reflects the latest state of localStorage or sessionStorage.useSyncExternalStore on how to listen for external changes. In this context, an “external change” occurs if another tab or a different hook instance updates the same storage key.storage event listener to the window. When a storage change event fires, the listener invokes a callback provided by React, signaling that an update has occurred. React then calls getSnapshot to fetch the new data and re-renders components if necessary.setValue function updates the browser storage and the internal cache. This enables components to write data back into localStorage or sessionStorage.setValue is invoked, the hook: localStorage or sessionStorage).useLocalStorage and useSessionStorage Build on useBrowserStorageBoth useLocalStorage and useSessionStorage are lightweight wrappers that utilize useBrowserStorage. Essentially, they invoke the same core hook—useBrowserStorage—but specify the storage type (localStorage or sessionStorage) to use. This separation of concerns allows you to create specialized hooks without duplicating the underlying logic.
In our demo app:
useLocalStorage is utilized for persistent data such as theme preferences (via a useThemeMode hook). This ensures that the user’s dark or light mode setting is remembered across browser sessions and tabs.useSessionStorage manages data that only needs to exist for the duration of the current browser tab or session, such as a selected account type. This selection persists through page reloads but resets when the tab or session ends.This approach neatly separates long-term data from session-only data without duplicating the underlying storage logic, making your codebase cleaner and more maintainable.
Persisting React state variables across reloads and tabs doesn’t have to be a tangled mess of stateful code and useEffects. With useSyncExternalStore, you gain a robust, concurrency-safe tool to:
By centralizing storage logic in a versatile useBrowserStorage hook and creating specialized hooks like useLocalStorage and useSessionStorage, you streamline your state management approach. This not only enhances code readability and maintainability but also ensures a seamless user experience as data persists reliably across various user actions.
Ready to elevate your React state management? Clone, edit, and play around with our Demo Repo. See firsthand how useSyncExternalStore can simplify persisting state across tabs and reloads. Embrace this powerful hook to build more resilient and user-friendly applications!
Happy Coding!
If you have questions or want to share how you’re using these hooks, feel free to reach out or open an issue in the demo repository. useSyncExternalStore is still a relatively new concept in React, so there’s plenty of room for the community to discover even more amazing use cases.