Zustand Cross-Tab State Sync

raw JSON →
0.2.3 verified Thu Apr 23 auth: no javascript

zustand-sync-tabs is a lightweight (~1KB minzipped) middleware for Zustand that facilitates seamless state synchronization across multiple browser tabs, windows, and iframes, provided they share the same origin. The current stable version is 0.2.3. It offers a 'fire and forget' setup, making it ideal for single-user applications that need consistent state across browsing contexts. Key differentiators include its small bundle size, full TypeScript support, and robust handling of one-writer/many-reader scenarios. It leverages the Broadcast Channel API (or localStorage fallback) for communication and supports both full and partial state sharing via include/exclude options for specific fields.

error TypeError: Cannot read properties of undefined (reading 'getState')
cause The `syncTabs` middleware expects a valid Zustand store creator function as its first argument.
fix
Ensure syncTabs wraps your store's (set) => ({ ... }) definition correctly, e.g., create(syncTabs(set => ({ ... }), { name: '...' })).
error State not syncing between tabs/windows.
cause This usually indicates that the 'name' option provided to `syncTabs` middleware is not identical across all browsing contexts, or the contexts are not on the same origin.
fix
Verify that the name property in the syncTabs options is an exact match (case-sensitive) for all instances of your store across different tabs. Also, confirm that all tabs are on the 'same origin'.
deprecated The `regExpToIgnore` option in SyncTabsOptionsType is deprecated and will be removed in future versions. Users should migrate to the `exclude` option, which accepts an array of strings or RegExp patterns for more flexible filtering.
fix Replace `regExpToIgnore: /pattern/` with `exclude: [/pattern/]` or `exclude: ['fieldName']` in your `syncTabs` options.
gotcha State synchronization is limited to tabs/windows/iframes that share the 'same origin'. Cross-origin communication is not supported due to browser security restrictions (e.g., between `example.com` and `sub.example.com` unless specifically configured via `document.domain`).
fix Ensure all browsing contexts requiring synchronization are hosted on the exact same origin (protocol, host, and port).
gotcha If using this middleware in combination with `zustand/persist`, ensure `syncTabs` is applied *inside* `persist` or configured carefully. The README recommends `persist-and-sync` if local persistence and cross-tab sync are both required, suggesting potential interaction complexities if managed independently.
fix For persistence and cross-tab sync, consider using `persist-and-sync` or carefully test the order of middleware application. A common pattern is `create(persist(syncTabs(...)))` or `create(syncTabs(persist(...)))` depending on desired interaction, but using a dedicated solution might be simpler.
npm install zustand-sync-tabs
yarn add zustand-sync-tabs
pnpm add zustand-sync-tabs

This quickstart demonstrates how to integrate `zustand-sync-tabs` middleware with a Zustand store. It shows a basic store definition, the application of `syncTabs` middleware with a channel name, and how to optionally exclude specific fields from synchronization. The store's `count` will sync across tabs, while `text` will remain local.

import { create } from 'zustand';
import { syncTabs } from 'zustand-sync-tabs';

type MyStore = {
  count: number;
  text: string;
  setCount: (n: number) => void;
  setText: (s: string) => void;
};

const useStore = create<MyStore>()(
  syncTabs(
    (set) => ({
      count: 0,
      text: 'Hello from main tab',
      setCount: (n) => set({ count: n }),
      setText: (s) => set({ text: s }),
    }),
    { 
      name: 'my-shared-channel', 
      exclude: ['text'] // Example: Exclude 'text' from syncing
    }
  )
);

// Example usage in a component or script:
// const count = useStore((state) => state.count);
// const setCount = useStore((state) => state.setCount);
// console.log('Current count:', count);