Zustand middleware to easily persist and sync Zustand state between tabs / windows / iframes (Same Origin)
Motivation: Recently I got caught up in several issues working with persist middleware and syncing tabs with Zustand. This is a simple light weight middleware to persist and instantly share state between tabs or windows
- β π (642 Bytes gZiped) ~ 0.5 kB size cross-tab state sharing + persistence for zustand
- β Full TypeScript Support
- β solid reliability in 1 writing and n reading tab-scenarios (with changing writing tab)
- β Fire and forget approach of always using the latest state. Perfect for single user systems
- β Share state between multiple browsing contexts
- β Additional control over which fields to persist-and-sync and which to ignore
- β Optimized for performance using memoization and closures.
$ pnpm add persist-and-sync
# or
$ npm install persist-and-sync
# or
$ yarn add persist-and-sync
Simply add the middleware while creating the store and the rest will be taken care.
import { create } from "zustand";
import { persistNSync } from "persist-and-sync";
type MyStore = {
count: number;
set: (n: number) => void;
};
const useStore = create<MyStore>(
persistNSync(
set => ({
count: 0,
set: n => set({ count: n }),
}),
{ name: "my-channel" },
),
);
β‘πBoom! Just a couple of lines and your state perfectly syncs between tabs/windows and it is also persisted using localStorage
!
In several cases you might want to exclude several fields from syncing. To support this scenario, we provide a mechanism to exclude fields based on list of fields or regular expression.
type PersistNSyncOptionsType = {
name: string;
/** @deprecated */
regExpToIgnore?: RegExp;
include?: (string | RegExp)[];
exclude?: (string | RegExp)[];
storage?: "localStorage" | "sessionStorage" | "cookies" /** Added in v1.1.0 */;
};
Example
export const useMyStore = create<MyStoreType>()(
persistNSync(
set => ({
count: 0,
_count: 0 /** skipped as it is included in exclude array */,
setCount: count => {
set(state => ({ ...state, count }));
},
set_Count: _count => {
set(state => ({ ...state, _count }));
},
}),
{ name: "example", exclude: ["_count"] },
),
);
It is good to note here that each element of
include
andexclude
array can either be a string or a regular expression. To use regular expression, you should either usenew RegExp()
or/your-expression/
syntax. Double or single quoted strings are not treated as regular expression. You can specify whether to use either"localStorage"
,"sessionStorage"
, or"cookies"
to persist the state - default"localStorage"
.
In several cases you might want to exclude several fields from syncing. To support this scenario, we provide a mechanism to exclude fields based on regExp. Just pass regExpToIgnore
(optional - default -> undefined) in options object.
// to ignore fields containing a slug
persistNSync(
set => ({
count: 0,
slugSomeState: 1,
slugSomeState2: 1,
set: n => set({ count: n }),
}),
{ name: "my-channel", regExpToIgnore: /slug/ },
// or regExpToIgnore: new RegExp('slug')
// Use full power of regExp by adding `i` and `g` flags
),
For more details about regExp check out - JS RegExp
For exactly matching a parameter/field use /^your-field-name$/
. ^
forces match from the first caracter and similarly, $
forces match until last character.
use regExpToIgnore: /^(field1|field2|field3)$/
π€© Don't forger to start this repo!
Want hands-on course for getting started with Turborepo? Check out React and Next.js with TypeScript and The Game of Chess with Next.js, React and TypeScrypt
Licensed as MIT open source.
with π by Mayank Kumar Chaudhari