@zap-studio/store-react 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [1.0.0]
8
+
9
+ ### Added
10
+
11
+ - First release. `useStore(store)` subscribes a component to a `createStore`/`derive` instance and re-renders on every change.
12
+ - `useStore(store, selector)` narrows the subscribed value; the component only re-renders when the selected result changes, compared with `Object.is`.
13
+ - Built on React's `useSyncExternalStore`, so subscriptions are safe under concurrent rendering.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexandre Trotel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @zap-studio/store-react
2
+
3
+ React bindings for [`@zap-studio/store`](https://www.npmjs.com/package/@zap-studio/store): a single `useStore` hook that subscribes a component to a `createStore`/`derive` instance.
4
+
5
+ Full documentation: [zapstudio.dev/store/react](https://www.zapstudio.dev/store/react)
6
+
7
+ ## Motivation
8
+
9
+ Zustand's React binding needs a `selector` plus a manual `shallow` equality check to avoid extra re-renders, because Zustand's own state has no built-in way to cache a derived value. `@zap-studio/store` already solves that at the source with `derive` — a computed value that is auto-tracked and cached, and only changes reference when it actually changes.
10
+
11
+ `@zap-studio/store-react` stays deliberately thin: one hook, built on React's own `useSyncExternalStore`, that reads whatever `@zap-studio/store` gives it. No middleware, no context Provider, no shallow-equality helper to reach for — build the exact value your component needs with `derive`, then subscribe to it.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @zap-studio/store-react @zap-studio/store
17
+ ```
18
+
19
+ ## Features
20
+
21
+ - **One hook**: `useStore(store, selector?)` — nothing else exported.
22
+ - **Works with both**: a `createStore` instance, or a `derive` value.
23
+ - **Built on `useSyncExternalStore`** — safe under React's concurrent rendering, no custom subscription-in-`useEffect` code.
24
+ - **`selector` is optional** and narrows the subscribed value; the component only re-renders when the selected result changes, compared with `Object.is`.
25
+
26
+ ## Quick Start
27
+
28
+ ```tsx
29
+ import { createStore, derive } from "@zap-studio/store";
30
+ import { useStore } from "@zap-studio/store-react";
31
+
32
+ const counter = createStore({ count: 0 }, (set) => ({
33
+ increment: () => set((s) => ({ count: s.count + 1 })),
34
+ }));
35
+
36
+ function Counter() {
37
+ const count = useStore(counter, (s) => s.count);
38
+ return <button onClick={() => counter.get().increment()}>{count}</button>;
39
+ }
40
+ ```
41
+
42
+ ## Without a Selector
43
+
44
+ `useStore(store)` re-renders on every change to `store` — both `createStore` and `derive` results are cached internally, so this is safe either way, it just re-renders on more changes than a selector would:
45
+
46
+ ```tsx
47
+ const isEven = derive([counter], (s) => s.count % 2 === 0);
48
+
49
+ function Parity() {
50
+ const even = useStore(isEven);
51
+ return even ? "even" : "odd";
52
+ }
53
+ ```
54
+
55
+ To avoid re-rendering on changes your component doesn't care about, pass a selector, or build the exact value you need with `derive` first.
56
+
57
+ ## Runtime Support
58
+
59
+ | Runtime | Minimum version |
60
+ | -------- | ------------------------------------------------ |
61
+ | React | 18.0.0 |
62
+ | Node.js | 18.0.0 |
63
+ | Bun | 1.0.0 |
64
+ | Deno | 1.42 |
65
+ | Browsers | Latest evergreen (Chrome, Edge, Firefox, Safari) |
66
+
67
+ ## License
68
+
69
+ MIT
@@ -0,0 +1,2 @@
1
+ import { useStore } from "./use-store.js";
2
+ export { useStore };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import { useStore } from "./use-store.js";
2
+ export { useStore };
@@ -0,0 +1,26 @@
1
+ import { Readable } from "@zap-studio/store";
2
+ //#region src/use-store.d.ts
3
+ /**
4
+ * Subscribes to a `createStore`/`derive` instance and re-renders when its
5
+ * value changes.
6
+ *
7
+ * Without `selector`, the component re-renders on every change to `store`.
8
+ * To re-render only on the parts your component actually reads, pass a
9
+ * `selector` — or build the exact value you need with `derive` first
10
+ * (`derive([store], (s) => s.count)`), rather than reaching for manual
11
+ * shallow-equality checks.
12
+ *
13
+ * @param store - A `createStore` or `derive` instance.
14
+ * @param selector - Narrows the subscribed value; re-renders only when its
15
+ * result changes (compared with `Object.is`).
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * const count = useStore(counter, (s) => s.count);
20
+ * ```
21
+ */
22
+ declare function useStore<T>(store: Readable<T>): T;
23
+ declare function useStore<T, U>(store: Readable<T>, selector: (value: T) => U): U;
24
+ //#endregion
25
+ export { useStore };
26
+ //# sourceMappingURL=use-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-store.d.ts","names":[],"sources":["../src/use-store.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;iBA8BgB,SAAS,GAAG,OAAO,SAAS,KAAK;iBACjC,SAAS,GAAG,GAAG,OAAO,SAAS,IAAI,WAAW,OAAO,MAAM,IAAI"}
@@ -0,0 +1,10 @@
1
+ import { useSyncExternalStore } from "react";
2
+ //#region src/use-store.ts
3
+ function useStore(store, selector) {
4
+ const getSnapshot = () => selector ? selector(store.get()) : store.get();
5
+ return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
6
+ }
7
+ //#endregion
8
+ export { useStore };
9
+
10
+ //# sourceMappingURL=use-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-store.js","names":[],"sources":["../src/use-store.ts"],"sourcesContent":["/**\n * `useStore`: subscribes a React component to a `createStore`/`derive`\n * instance, re-rendering only when the subscribed value actually changes.\n *\n * @module @zap-studio/store-react/use-store\n */\n\nimport type { Readable } from \"@zap-studio/store\";\n\nimport { useSyncExternalStore } from \"react\";\n\n/**\n * Subscribes to a `createStore`/`derive` instance and re-renders when its\n * value changes.\n *\n * Without `selector`, the component re-renders on every change to `store`.\n * To re-render only on the parts your component actually reads, pass a\n * `selector` — or build the exact value you need with `derive` first\n * (`derive([store], (s) => s.count)`), rather than reaching for manual\n * shallow-equality checks.\n *\n * @param store - A `createStore` or `derive` instance.\n * @param selector - Narrows the subscribed value; re-renders only when its\n * result changes (compared with `Object.is`).\n *\n * @example\n * ```tsx\n * const count = useStore(counter, (s) => s.count);\n * ```\n */\nexport function useStore<T>(store: Readable<T>): T;\nexport function useStore<T, U>(store: Readable<T>, selector: (value: T) => U): U;\nexport function useStore<T, U>(store: Readable<T>, selector?: (value: T) => U): T | U {\n const getSnapshot = (): T | U => (selector ? selector(store.get()) : store.get());\n return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n"],"mappings":";;AAgCA,SAAgB,SAAe,OAAoB,UAAmC;CACpF,MAAM,oBAA4B,WAAW,SAAS,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI;CAC/E,OAAO,qBAAqB,MAAM,WAAW,aAAa,WAAW;AACvE"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@zap-studio/store-react",
3
+ "version": "1.0.0",
4
+ "private": false,
5
+ "description": "React bindings for @zap-studio/store: a useStore hook that subscribes to a createStore/derive instance.",
6
+ "keywords": [
7
+ "derive",
8
+ "react",
9
+ "react-hooks",
10
+ "state",
11
+ "state-management",
12
+ "store",
13
+ "typescript"
14
+ ],
15
+ "homepage": "https://www.zapstudio.dev/store/react",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/zap-studio/monorepo.git",
20
+ "directory": "packages/store-react"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "CHANGELOG.md",
25
+ "LICENSE",
26
+ "README.md"
27
+ ],
28
+ "type": "module",
29
+ "sideEffects": false,
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": "./dist/index.js",
33
+ "./use-store": "./dist/use-store.js",
34
+ "./package.json": "./package.json"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "prepublishOnly": "pnpm -C ../.. run build"
41
+ },
42
+ "devDependencies": {
43
+ "@testing-library/dom": "catalog:",
44
+ "@testing-library/react": "catalog:",
45
+ "@types/react": "catalog:",
46
+ "@types/react-dom": "catalog:",
47
+ "@zap-studio/store": "workspace:*",
48
+ "@zap-studio/typescript": "workspace:*",
49
+ "react": "catalog:",
50
+ "react-dom": "catalog:",
51
+ "tsdown": "catalog:",
52
+ "typescript": "catalog:",
53
+ "vitest": "catalog:"
54
+ },
55
+ "peerDependencies": {
56
+ "@zap-studio/store": "^1.0.0",
57
+ "react": ">=18.0.0"
58
+ },
59
+ "engines": {
60
+ "node": ">=18.0.0"
61
+ }
62
+ }