@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 +13 -0
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/use-store.d.ts +26 -0
- package/dist/use-store.d.ts.map +1 -0
- package/dist/use-store.js +10 -0
- package/dist/use-store.js.map +1 -0
- package/package.json +62 -0
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
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -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
|
+
}
|