Skip to content

Commit

Permalink
Merge pull request #11 from mayank1513/zustand-sync
Browse files Browse the repository at this point in the history
Zustand sync
  • Loading branch information
mayank1513 committed Aug 26, 2023
2 parents a4862ee + 5761879 commit 7e7fa58
Show file tree
Hide file tree
Showing 14 changed files with 280 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/eight-donuts-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"persistnsync": patch
---

Update keywords
22 changes: 22 additions & 0 deletions .github/workflows/publish-to-npm-on-new-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,28 @@ jobs:
with:
registry-url: https://registry.npmjs.org

- name: Publish Zustand-Sync to NPM
id: syncZustand
working-directory: ./packages/zustand-sync
continue-on-error: true
run: pnpm build && pnpm publish-package && pnpm pp2 && pnpm pp3 && pnpm pp4
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
- uses: actions/setup-node@v3
if: steps.syncZustand.outcome == 'success'
with:
registry-url: https://npm.pkg.github.com/
- name: Publish to GitHub Public Repository
if: steps.syncZustand.outcome == 'success'
working-directory: ./packages/zustand-sync
run: pnpm p-gpr && pnpm pp2 && pnpm pp3 && pnpm pp4
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
- uses: actions/setup-node@v3
if: steps.syncZustand.outcome == 'success'
with:
registry-url: https://registry.npmjs.org

- name: Publish nextjs-themes to NPM
run: pnpm build && pnpm publish-package && pnpm publish-package2
env:
Expand Down
5 changes: 5 additions & 0 deletions packages/persistnsync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@
"api",
"broadcast",
"channel",
"sync-tabs",
"sync-windows",
"sync",
"broadcast-channel",
"persist",
"localStorage",
"hooks",
"react",
"react 18",
Expand Down
4 changes: 2 additions & 2 deletions packages/persistnsync/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { StateCreator } from "zustand";

export type PersistNSyncTypeOptions = { name: string };
type PersistNSyncType = <T>(f: StateCreator<T, [], []>, options: PersistNSyncTypeOptions) => StateCreator<T, [], []>;
export type PersistNSyncOptionsType = { name: string };
type PersistNSyncType = <T>(f: StateCreator<T, [], []>, options: PersistNSyncOptionsType) => StateCreator<T, [], []>;

export const persistNSync: PersistNSyncType = (f, options) => (set, get, store) => {
const { name } = options;
Expand Down
57 changes: 57 additions & 0 deletions packages/zustand-sync/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Zustand Sync Tabs [![Version](https://img.shields.io/npm/v/zustand-sync.svg?colorB=green)](https://www.npmjs.com/package/zustand-sync) [![Downloads](https://img.jsdelivr.com/img.shields.io/npm/dt/zustand-sync.svg)](https://www.npmjs.com/package/zustand-sync) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/zustand-sync)

> Zustand middleware to easily persist and sync Zustand state between tabs / windows / iframes (SameOrigin)
> Motivation: Recently I got cought up in several issues working with persist miggleware and syncing tabs with zustand. This is a simple light weight middleware to persist and instantly share state between tabs or windows
- ✅ 🐙 (495 Bytes gZiped) < 0.5 kB size cross-tab state sharing middleware 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
- ✅ Sync Zustand state between multiple browsing contexts

> Checkout `[persistnsync](https://github.com/mayank1513/nextjs-themes/tree/main/packages/persistnsync#readme)` if you are looking for persisting state locally over reload/refresh or after closing site
## Install

```bash
$ pnpm add zustand-sync
# or
$ npm install zustand-sync
# or
$ yarn add zustand-sync
```

## Usage

Simply add the middleware while creating the store and the rest will be taken care.

```ts
import { create } from "zustand";
import { syncTabs } from "zustand-sync";

type MyStore = {
count: number;
set: (n: number) => void;
};

const useStore = create<MyStore>(
syncTabs(
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`!

## License

Licensed as MIT open source.

<hr />

<p align="center" style="text-align:center">with 💖 by <a href="https://mayank-chaudhari.vercel.app" target="_blank">Mayank Kumar Chaudhari</a></p>
11 changes: 11 additions & 0 deletions packages/zustand-sync/createPackageJSON.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use strict";

const fs = require("fs");
const path = require("path");
const packageJson = require(path.resolve(__dirname, "package.json"));

const { devDependencies, scripts, ...newPackageJSON } = packageJson;
newPackageJSON.main = packageJson.main.split("/")[1];
newPackageJSON.types = packageJson.types.split("/")[1];

fs.writeFileSync(path.resolve(__dirname, "dist", "package.json"), JSON.stringify(newPackageJSON, null, 2));
52 changes: 52 additions & 0 deletions packages/zustand-sync/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "zustand-sync",
"author": "Mayank Kumar Chaudhari <https://mayank-chaudhari.vercel.app>",
"version": "0.0.0",
"description": "Zustand middleware to easily sync Zustand state between tabs and windows",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": "https://github.com/mayank1513/nextjs-themes/tree/main/packages/zustand-sync",
"homepage": "https://github.com/mayank1513/nextjs-themes/tree/main/packages/zustand-sync#readme",
"sideEffects": false,
"license": "MIT",
"scripts": {
"build": "tsc && node createPackageJSON.js",
"publish-package": "cp README.md dist && cd dist && npm publish && cd ..",
"pp2": "node seo.js && cd dist && npm publish && cd ..",
"pp3": "node seo1.js && cd dist && npm publish && cd ..",
"pp4": "node seo2.js && cd dist && npm publish && cd ..",
"p-gpr": "node createPackageJSON.js && cp README.md dist && node prepGPR.js && cd dist && npm publish && cd .."
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/mayank1513"
},
"devDependencies": {
"@types/node": "^20.4.5",
"typescript": "^5.1.6",
"zustand": "^4.4.1"
},
"peerDependencies": {
"zustand": "^3 || ^4"
},
"keywords": [
"web",
"api",
"broadcast",
"channel",
"sync-tabs",
"sync-windows",
"sync",
"broadcast-channel",
"hooks",
"react",
"react 18",
"zustand",
"middleware",
"state",
"optimized",
"tiny",
"typescript",
"javascript"
]
}
12 changes: 12 additions & 0 deletions packages/zustand-sync/prepGPR.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use strict";

const fs = require("fs");
const path = require("path");

const packageJsonPath = path.resolve(__dirname, "dist", "package.json");
const packageJson = require(packageJsonPath);
packageJson.name = `@mayank1513/${packageJson.name}`;
packageJson.publishConfig = {
"@mayank1513:registry": "https://npm.pkg.github.com",
};
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
17 changes: 17 additions & 0 deletions packages/zustand-sync/seo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use strict";

const fs = require("fs");
const path = require("path");

const name = "zustand-sync-tabs";
const ref = "zustand-sync";

const packageJsonPath = path.resolve(__dirname, "dist", "package.json");
const packageJson = require(packageJsonPath);
packageJson.name = packageJson.name.replace(ref, name);
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));

const readMePath = path.resolve(__dirname, "dist", "README.md");
let readMe = fs.readFileSync(readMePath, { encoding: "utf8" });
readMe = readMe.replace(new RegExp(ref, "g"), name);
fs.writeFileSync(readMePath, readMe);
17 changes: 17 additions & 0 deletions packages/zustand-sync/seo1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use strict";

const fs = require("fs");
const path = require("path");

const name = "sync-zustand";
const ref = "zustand-sync-tabs";

const packageJsonPath = path.resolve(__dirname, "dist", "package.json");
const packageJson = require(packageJsonPath);
packageJson.name = packageJson.name.replace(ref, name);
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));

const readMePath = path.resolve(__dirname, "dist", "README.md");
let readMe = fs.readFileSync(readMePath, { encoding: "utf8" });
readMe = readMe.replace(new RegExp(ref, "g"), name);
fs.writeFileSync(readMePath, readMe);
17 changes: 17 additions & 0 deletions packages/zustand-sync/seo2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use strict";

const fs = require("fs");
const path = require("path");

const name = "sync-tabs-zustand";
const ref = "sync-zustand";

const packageJsonPath = path.resolve(__dirname, "dist", "package.json");
const packageJson = require(packageJsonPath);
packageJson.name = packageJson.name.replace(ref, name);
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));

const readMePath = path.resolve(__dirname, "dist", "README.md");
let readMe = fs.readFileSync(readMePath, { encoding: "utf8" });
readMe = readMe.replace(new RegExp(ref, "g"), name);
fs.writeFileSync(readMePath, readMe);
37 changes: 37 additions & 0 deletions packages/zustand-sync/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { StateCreator } from "zustand";

export type SyncTabsOptionsType = { name: string };
type SyncTabsType = <T>(f: StateCreator<T, [], []>, options: SyncTabsOptionsType) => StateCreator<T, [], []>;

export const syncTabs: SyncTabsType = (f, options) => (set, get, store) => {
/** avoid errors on server side or when BroadcastChannel is not supported */
if (!globalThis.BroadcastChannel) {
console.log("BroadcastChannel is not supported in this context!");
return f(set, get, store);
}

const channel = new BroadcastChannel(options.name);

const set_: typeof set = (...args) => {
const prevState = get() as { [k: string]: any };
set(...args);
const currentState = get() as { [k: string]: any };
const stateUpdates: { [k: string]: any } = {};
/** sync only updated state to avoid un-necessary re-renders */
Object.keys(currentState).forEach(k => {
if (currentState[k] !== prevState[k]) {
stateUpdates[k] = currentState[k];
}
});
if (Object.keys(stateUpdates).length) {
channel?.postMessage(stateUpdates);
}
};

if (channel) {
channel.onmessage = e => {
set(e.data);
};
}
return f(set_, get, store);
};
14 changes: 14 additions & 0 deletions packages/zustand-sync/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"declaration": true,
"esModuleInterop": true,
"moduleResolution": "node",
"skipLibCheck": true,
"module": "CommonJS",
"strict": true,
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7e7fa58

Please sign in to comment.