@t8/react-store 1.0.37 → 1.1.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/README.md +3 -6
- package/dist/index.js +25 -7
- package/dist/src/useStore.d.ts +16 -9
- package/package.json +9 -10
- package/src/useStore.ts +33 -20
package/README.md
CHANGED
|
@@ -90,13 +90,10 @@ Apart from a boolean, `useStore(store, shouldUpdate)` accepts a function of `(ne
|
|
|
90
90
|
import { useStore } from "@t8/react-store";
|
|
91
91
|
|
|
92
92
|
let ItemCard = ({ id }) => {
|
|
93
|
-
|
|
94
|
-
let hasRelevantUpdates = useCallback((nextItems, prevItems) => {
|
|
93
|
+
let [items, setItems] = useStore(itemStore, (nextItems, prevItems) => {
|
|
95
94
|
// Assuming that items have a `revision` property
|
|
96
95
|
return nextItems[id].revision !== prevItems[id].revision;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
let [items, setItems] = useStore(itemStore, hasRelevantUpdates);
|
|
96
|
+
});
|
|
100
97
|
|
|
101
98
|
return (
|
|
102
99
|
// Content
|
|
@@ -104,7 +101,7 @@ let ItemCard = ({ id }) => {
|
|
|
104
101
|
};
|
|
105
102
|
```
|
|
106
103
|
|
|
107
|
-
While `useStore(itemStore)` in this component would trigger a re-render in response to any changes in the `itemStore` (which can be fine with a small store), with `useStore(itemStore,
|
|
104
|
+
While `useStore(itemStore)` in this component would trigger a re-render in response to any changes in the `itemStore` (which can be fine with a small store), with `useStore(itemStore, shouldUpdate)` the `ItemCard` component has a more targeted subscription to the store: in this example, a re-render will only be triggered if the `revision` property of the item with the given `id` has changed.
|
|
108
105
|
|
|
109
106
|
## Providing shared state
|
|
110
107
|
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
1
|
// node_modules/@t8/store/src/isStore.ts
|
|
2
2
|
function isStore(x) {
|
|
3
|
-
return x !== null && typeof x === "object" && "
|
|
3
|
+
return x !== null && typeof x === "object" && "on" in x && typeof x.on === "function" && "getState" in x && typeof x.getState === "function" && "setState" in x && typeof x.setState === "function";
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
// node_modules/@t8/store/src/Store.ts
|
|
7
7
|
var Store = class {
|
|
8
8
|
state;
|
|
9
|
-
callbacks =
|
|
9
|
+
callbacks = {};
|
|
10
10
|
revision = -1;
|
|
11
11
|
constructor(data) {
|
|
12
12
|
this.state = data;
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
this.callbacks.add(callback);
|
|
14
|
+
on(event, callback) {
|
|
15
|
+
(this.callbacks[event] ??= /* @__PURE__ */ new Set()).add(callback);
|
|
16
16
|
return () => {
|
|
17
|
-
this.
|
|
17
|
+
this.off(event, callback);
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
off(event, callback) {
|
|
21
|
+
this.callbacks[event]?.delete(callback);
|
|
22
|
+
}
|
|
23
|
+
once(event, callback) {
|
|
24
|
+
let oneTimeCallback = (nextState, prevState) => {
|
|
25
|
+
this.off(event, oneTimeCallback);
|
|
26
|
+
callback(nextState, prevState);
|
|
27
|
+
};
|
|
28
|
+
return this.on(event, oneTimeCallback);
|
|
29
|
+
}
|
|
30
|
+
emit(event, nextState, prevState) {
|
|
31
|
+
let eventCallbacks = this.callbacks[event];
|
|
32
|
+
if (eventCallbacks) {
|
|
33
|
+
for (let callback of eventCallbacks)
|
|
34
|
+
callback(nextState ?? this.state, prevState ?? this.state);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
20
37
|
getState() {
|
|
21
38
|
return this.state;
|
|
22
39
|
}
|
|
@@ -25,7 +42,7 @@ var Store = class {
|
|
|
25
42
|
let nextState = update instanceof Function ? update(this.state) : update;
|
|
26
43
|
this.state = nextState;
|
|
27
44
|
this.revision = Math.random();
|
|
28
|
-
|
|
45
|
+
this.emit("update", nextState, prevState);
|
|
29
46
|
}
|
|
30
47
|
};
|
|
31
48
|
|
|
@@ -38,8 +55,9 @@ function useStore(store, shouldUpdate = true) {
|
|
|
38
55
|
let setState = useMemo(() => store.setState.bind(store), [store]);
|
|
39
56
|
let initialStoreRevision = useRef(store.revision);
|
|
40
57
|
useEffect(() => {
|
|
58
|
+
store.emit("effect");
|
|
41
59
|
if (!shouldUpdate) return;
|
|
42
|
-
let unsubscribe = store.
|
|
60
|
+
let unsubscribe = store.on("update", (nextState, prevState) => {
|
|
43
61
|
if (typeof shouldUpdate !== "function" || shouldUpdate(nextState, prevState))
|
|
44
62
|
setRevision(Math.random());
|
|
45
63
|
});
|
package/dist/src/useStore.d.ts
CHANGED
|
@@ -2,24 +2,31 @@ import { type Store } from "@t8/store";
|
|
|
2
2
|
export type SetStoreState<T> = Store<T>["setState"];
|
|
3
3
|
export type ShouldUpdateCallback<T> = (nextState: T, prevState: T) => boolean;
|
|
4
4
|
export type ShouldUpdate<T> = boolean | ShouldUpdateCallback<T>;
|
|
5
|
-
export declare function useStore<T>(store: Store<T>,
|
|
6
5
|
/**
|
|
7
|
-
*
|
|
8
|
-
* to the store
|
|
6
|
+
* Returns the state value of `store` passed as the parameter and
|
|
7
|
+
* a function to update the store state value.
|
|
9
8
|
*
|
|
10
|
-
* @
|
|
9
|
+
* @example
|
|
10
|
+
* ```js
|
|
11
|
+
* let [value, setValue] = useStore(store);
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The optional second parameter `shouldUpdate` controls whether
|
|
15
|
+
* the component using this hook should be updated in response to
|
|
16
|
+
* the store updates, which is set to `true` by default.
|
|
11
17
|
*
|
|
12
18
|
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
13
|
-
* to the store updates. Use case:
|
|
19
|
+
* to the store updates. Use case: if the component only requires
|
|
14
20
|
* the store state setter but not the store state value, the
|
|
15
|
-
* component may not need to respond to the store updates:
|
|
21
|
+
* component may not need to respond to the store updates at all:
|
|
16
22
|
*
|
|
17
|
-
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```js
|
|
18
25
|
* let [, setValue] = useStore(store, false);
|
|
19
26
|
* ```
|
|
20
27
|
*
|
|
21
|
-
* `shouldUpdate` can be
|
|
28
|
+
* `shouldUpdate` can also be a function `(nextState, prevState) => boolean`
|
|
22
29
|
* to make the component respond only to specific store state changes,
|
|
23
30
|
* when this function returns `true`.
|
|
24
31
|
*/
|
|
25
|
-
shouldUpdate?: ShouldUpdate<T>): [T, SetStoreState<T>];
|
|
32
|
+
export declare function useStore<T>(store: Store<T>, shouldUpdate?: ShouldUpdate<T>): [T, SetStoreState<T>];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t8/react-store",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Small React app state management lib aligned with React's state pattern, condensed to the essentials",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,11 +11,10 @@
|
|
|
11
11
|
"compile": "npx esbuild index.ts --bundle --outdir=dist --platform=neutral --external:react",
|
|
12
12
|
"demo": "npx @t8/serve 3000 * tests/counter -b src/index.tsx",
|
|
13
13
|
"prepublishOnly": "npm run build",
|
|
14
|
-
"preversion": "npx npm-run-all
|
|
15
|
-
"shape": "npx codeshape",
|
|
14
|
+
"preversion": "npx npm-run-all shape build test",
|
|
15
|
+
"shape": "npx codeshape --typecheck",
|
|
16
16
|
"test": "npx playwright test --project=chromium",
|
|
17
17
|
"tic-tac-toe": "npx @t8/serve 3000 * tests/tic-tac-toe -b src/index.tsx",
|
|
18
|
-
"typecheck": "tsc --noEmit",
|
|
19
18
|
"types": "tsc -p tsconfig.build.json"
|
|
20
19
|
},
|
|
21
20
|
"repository": {
|
|
@@ -37,15 +36,15 @@
|
|
|
37
36
|
},
|
|
38
37
|
"devDependencies": {
|
|
39
38
|
"@playwright/test": "^1.56.0",
|
|
40
|
-
"@t8/serve": "^0.1.
|
|
39
|
+
"@t8/serve": "^0.1.36",
|
|
41
40
|
"@types/node": "^24.5.2",
|
|
42
|
-
"@types/react": "^19.
|
|
43
|
-
"@types/react-dom": "^19.
|
|
44
|
-
"immer": "^
|
|
45
|
-
"react-dom": "^19.
|
|
41
|
+
"@types/react": "^19.2.7",
|
|
42
|
+
"@types/react-dom": "^19.2.3",
|
|
43
|
+
"immer": "^11.0.1",
|
|
44
|
+
"react-dom": "^19.2.1",
|
|
46
45
|
"typescript": "^5.9.3"
|
|
47
46
|
},
|
|
48
47
|
"dependencies": {
|
|
49
|
-
"@t8/store": "^1.
|
|
48
|
+
"@t8/store": "^1.2.0"
|
|
50
49
|
}
|
|
51
50
|
}
|
package/src/useStore.ts
CHANGED
|
@@ -5,27 +5,35 @@ export type SetStoreState<T> = Store<T>["setState"];
|
|
|
5
5
|
export type ShouldUpdateCallback<T> = (nextState: T, prevState: T) => boolean;
|
|
6
6
|
export type ShouldUpdate<T> = boolean | ShouldUpdateCallback<T>;
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Returns the state value of `store` passed as the parameter and
|
|
10
|
+
* a function to update the store state value.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```js
|
|
14
|
+
* let [value, setValue] = useStore(store);
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* The optional second parameter `shouldUpdate` controls whether
|
|
18
|
+
* the component using this hook should be updated in response to
|
|
19
|
+
* the store updates, which is set to `true` by default.
|
|
20
|
+
*
|
|
21
|
+
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
22
|
+
* to the store updates. Use case: if the component only requires
|
|
23
|
+
* the store state setter but not the store state value, the
|
|
24
|
+
* component may not need to respond to the store updates at all:
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```js
|
|
28
|
+
* let [, setValue] = useStore(store, false);
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* `shouldUpdate` can also be a function `(nextState, prevState) => boolean`
|
|
32
|
+
* to make the component respond only to specific store state changes,
|
|
33
|
+
* when this function returns `true`.
|
|
34
|
+
*/
|
|
8
35
|
export function useStore<T>(
|
|
9
36
|
store: Store<T>,
|
|
10
|
-
/**
|
|
11
|
-
* Controls whether the component should be updated in response
|
|
12
|
-
* to the store updates.
|
|
13
|
-
*
|
|
14
|
-
* @defaultValue `true`
|
|
15
|
-
*
|
|
16
|
-
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
17
|
-
* to the store updates. Use case: when the component only requires
|
|
18
|
-
* the store state setter but not the store state value, the
|
|
19
|
-
* component may not need to respond to the store updates:
|
|
20
|
-
*
|
|
21
|
-
* ```ts
|
|
22
|
-
* let [, setValue] = useStore(store, false);
|
|
23
|
-
* ```
|
|
24
|
-
*
|
|
25
|
-
* `shouldUpdate` can be set to a function `(nextState, prevState) => boolean`
|
|
26
|
-
* to make the component respond only to specific store state changes,
|
|
27
|
-
* when this function returns `true`.
|
|
28
|
-
*/
|
|
29
37
|
shouldUpdate: ShouldUpdate<T> = true,
|
|
30
38
|
): [T, SetStoreState<T>] {
|
|
31
39
|
if (!isStore(store)) throw new Error("'store' is not an instance of Store");
|
|
@@ -37,9 +45,14 @@ export function useStore<T>(
|
|
|
37
45
|
let initialStoreRevision = useRef(store.revision);
|
|
38
46
|
|
|
39
47
|
useEffect(() => {
|
|
48
|
+
// Use case: a one-time subscription to this event allows to
|
|
49
|
+
// initialize the store state on the client without causing a
|
|
50
|
+
// hydration error.
|
|
51
|
+
store.emit("effect");
|
|
52
|
+
|
|
40
53
|
if (!shouldUpdate) return;
|
|
41
54
|
|
|
42
|
-
let unsubscribe = store.
|
|
55
|
+
let unsubscribe = store.on("update", (nextState, prevState) => {
|
|
43
56
|
if (
|
|
44
57
|
typeof shouldUpdate !== "function" ||
|
|
45
58
|
shouldUpdate(nextState, prevState)
|