Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zustand sync #11

Merged
merged 3 commits into from
Aug 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.