@t8/react-store 1.2.3 → 1.2.5
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 +1 -1
- package/dist/index.cjs +60 -0
- package/dist/index.d.ts +35 -1
- package/dist/index.mjs +56 -0
- package/package.json +10 -15
- package/dist/index.js +0 -152
- package/dist/src/useStore.d.ts +0 -32
- package/tsconfig.build.json +0 -4
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@t8/react-store)   
|
|
6
6
|
|
|
7
|
-
**Why?** To have an easy-to-use state management lib for React apps requiring least effort to migrate from local state and to quickly set up shared state from scratch, whether with SSR or without. Other approaches,
|
|
7
|
+
**Why?** To have an easy-to-use state management lib for React apps requiring least effort to migrate from local state and to quickly set up shared state from scratch, whether with SSR or without. Other approaches, like Redux Toolkit, Zustand, Jotai, MobX, depart from this picture to varying degrees.
|
|
8
8
|
|
|
9
9
|
This picture is achieved here by (1) having a simple API introducing as few new entities as possible, (2) closely following the React's `useState()` pattern of initializing and manipulating the state to avoid boilerplate and sizable rewrites in the common task of migration from local state to shared state, (3) working smoothly with SSR with regular React Contexts without requiring a specifically designed setup and without internally making use of global stores or other global variables by default.
|
|
10
10
|
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
let __t8_store = require("@t8/store");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
|
|
4
|
+
//#region src/useStore.ts
|
|
5
|
+
/**
|
|
6
|
+
* Returns the state value of `store` passed as the parameter and
|
|
7
|
+
* a function to update the store state value.
|
|
8
|
+
*
|
|
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.
|
|
17
|
+
*
|
|
18
|
+
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
19
|
+
* to the store updates. Use case: if the component only requires
|
|
20
|
+
* the store state setter but not the store state value, the
|
|
21
|
+
* component may not need to respond to the store updates at all:
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```js
|
|
25
|
+
* let [, setValue] = useStore(store, false);
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* `shouldUpdate` can also be a function `(nextState, prevState) => boolean`
|
|
29
|
+
* to make the component respond only to specific store state changes,
|
|
30
|
+
* when this function returns `true`.
|
|
31
|
+
*/
|
|
32
|
+
function useStore(store, shouldUpdate = true) {
|
|
33
|
+
if (!(0, __t8_store.isStore)(store)) throw new Error("'store' is not an instance of Store");
|
|
34
|
+
let [, setRevision] = (0, react.useState)(-1);
|
|
35
|
+
let state = store.getState();
|
|
36
|
+
let setState = (0, react.useMemo)(() => store.setState.bind(store), [store]);
|
|
37
|
+
let initialStoreRevision = (0, react.useRef)(store.revision);
|
|
38
|
+
(0, react.useEffect)(() => {
|
|
39
|
+
if ((0, __t8_store.isPersistentStore)(store)) store.syncOnce();
|
|
40
|
+
if (!shouldUpdate) return;
|
|
41
|
+
let unsubscribe = store.onUpdate((nextState, prevState) => {
|
|
42
|
+
if (typeof shouldUpdate !== "function" || shouldUpdate(nextState, prevState)) setRevision(Math.random());
|
|
43
|
+
});
|
|
44
|
+
if (store.revision !== initialStoreRevision.current) setRevision(Math.random());
|
|
45
|
+
return () => {
|
|
46
|
+
unsubscribe();
|
|
47
|
+
initialStoreRevision.current = store.revision;
|
|
48
|
+
};
|
|
49
|
+
}, [store, shouldUpdate]);
|
|
50
|
+
return [state, setState];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//#endregion
|
|
54
|
+
exports.useStore = useStore;
|
|
55
|
+
Object.keys(__t8_store).forEach(function (k) {
|
|
56
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
57
|
+
enumerable: true,
|
|
58
|
+
get: function () { return __t8_store[k]; }
|
|
59
|
+
});
|
|
60
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,36 @@
|
|
|
1
|
+
import { Store } from "@t8/store";
|
|
1
2
|
export * from "@t8/store";
|
|
2
|
-
|
|
3
|
+
|
|
4
|
+
type SetStoreState<T> = Store<T>["setState"];
|
|
5
|
+
type ShouldUpdateCallback<T> = (nextState: T, prevState: T) => boolean;
|
|
6
|
+
type ShouldUpdate<T> = boolean | ShouldUpdateCallback<T>;
|
|
7
|
+
/**
|
|
8
|
+
* Returns the state value of `store` passed as the parameter and
|
|
9
|
+
* a function to update the store state value.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```js
|
|
13
|
+
* let [value, setValue] = useStore(store);
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The optional second parameter `shouldUpdate` controls whether
|
|
17
|
+
* the component using this hook should be updated in response to
|
|
18
|
+
* the store updates, which is set to `true` by default.
|
|
19
|
+
*
|
|
20
|
+
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
21
|
+
* to the store updates. Use case: if the component only requires
|
|
22
|
+
* the store state setter but not the store state value, the
|
|
23
|
+
* component may not need to respond to the store updates at all:
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```js
|
|
27
|
+
* let [, setValue] = useStore(store, false);
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* `shouldUpdate` can also be a function `(nextState, prevState) => boolean`
|
|
31
|
+
* to make the component respond only to specific store state changes,
|
|
32
|
+
* when this function returns `true`.
|
|
33
|
+
*/
|
|
34
|
+
declare function useStore<T>(store: Store<T>, shouldUpdate?: ShouldUpdate<T>): [T, SetStoreState<T>];
|
|
35
|
+
|
|
36
|
+
export { SetStoreState, ShouldUpdate, ShouldUpdateCallback, useStore };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isPersistentStore, isStore } from "@t8/store";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
export * from "@t8/store"
|
|
5
|
+
|
|
6
|
+
//#region src/useStore.ts
|
|
7
|
+
/**
|
|
8
|
+
* Returns the state value of `store` passed as the parameter and
|
|
9
|
+
* a function to update the store state value.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```js
|
|
13
|
+
* let [value, setValue] = useStore(store);
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The optional second parameter `shouldUpdate` controls whether
|
|
17
|
+
* the component using this hook should be updated in response to
|
|
18
|
+
* the store updates, which is set to `true` by default.
|
|
19
|
+
*
|
|
20
|
+
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
21
|
+
* to the store updates. Use case: if the component only requires
|
|
22
|
+
* the store state setter but not the store state value, the
|
|
23
|
+
* component may not need to respond to the store updates at all:
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```js
|
|
27
|
+
* let [, setValue] = useStore(store, false);
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* `shouldUpdate` can also be a function `(nextState, prevState) => boolean`
|
|
31
|
+
* to make the component respond only to specific store state changes,
|
|
32
|
+
* when this function returns `true`.
|
|
33
|
+
*/
|
|
34
|
+
function useStore(store, shouldUpdate = true) {
|
|
35
|
+
if (!isStore(store)) throw new Error("'store' is not an instance of Store");
|
|
36
|
+
let [, setRevision] = useState(-1);
|
|
37
|
+
let state = store.getState();
|
|
38
|
+
let setState = useMemo(() => store.setState.bind(store), [store]);
|
|
39
|
+
let initialStoreRevision = useRef(store.revision);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (isPersistentStore(store)) store.syncOnce();
|
|
42
|
+
if (!shouldUpdate) return;
|
|
43
|
+
let unsubscribe = store.onUpdate((nextState, prevState) => {
|
|
44
|
+
if (typeof shouldUpdate !== "function" || shouldUpdate(nextState, prevState)) setRevision(Math.random());
|
|
45
|
+
});
|
|
46
|
+
if (store.revision !== initialStoreRevision.current) setRevision(Math.random());
|
|
47
|
+
return () => {
|
|
48
|
+
unsubscribe();
|
|
49
|
+
initialStoreRevision.current = store.revision;
|
|
50
|
+
};
|
|
51
|
+
}, [store, shouldUpdate]);
|
|
52
|
+
return [state, setState];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { useStore };
|
package/package.json
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t8/react-store",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
4
4
|
"description": "Small React app state management lib aligned with React's state pattern, condensed to the essentials",
|
|
5
|
-
"main": "dist/index.
|
|
6
|
-
"
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
7
8
|
"type": "module",
|
|
8
9
|
"scripts": {
|
|
9
|
-
"build": "npx npm-run-all clean compile types",
|
|
10
|
-
"clean": "node -e \"require('node:fs').rmSync('dist', { force: true, recursive: true });\"",
|
|
11
|
-
"compile": "npx esbuild index.ts --bundle --outdir=dist --platform=neutral --external:react",
|
|
12
10
|
"demo": "npx @t8/serve 3000 * tests/counter -b src/index.tsx",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"shape": "npx codeshape --typecheck",
|
|
11
|
+
"preversion": "npx npm-run-all shape test",
|
|
12
|
+
"shape": "npx codeshape",
|
|
16
13
|
"test": "npx playwright test --project=chromium",
|
|
17
|
-
"tic-tac-toe": "npx @t8/serve 3000 * tests/tic-tac-toe -b src/index.tsx"
|
|
18
|
-
"types": "tsc -p tsconfig.build.json"
|
|
14
|
+
"tic-tac-toe": "npx @t8/serve 3000 * tests/tic-tac-toe -b src/index.tsx"
|
|
19
15
|
},
|
|
20
16
|
"repository": {
|
|
21
17
|
"type": "git",
|
|
@@ -36,15 +32,14 @@
|
|
|
36
32
|
},
|
|
37
33
|
"devDependencies": {
|
|
38
34
|
"@playwright/test": "^1.56.0",
|
|
39
|
-
"@t8/serve": "^0.1.
|
|
35
|
+
"@t8/serve": "^0.1.37",
|
|
40
36
|
"@types/node": "^24.5.2",
|
|
41
37
|
"@types/react": "^19.2.7",
|
|
42
38
|
"@types/react-dom": "^19.2.3",
|
|
43
39
|
"immer": "^11.0.1",
|
|
44
|
-
"react-dom": "^19.2.1"
|
|
45
|
-
"typescript": "^5.9.3"
|
|
40
|
+
"react-dom": "^19.2.1"
|
|
46
41
|
},
|
|
47
42
|
"dependencies": {
|
|
48
|
-
"@t8/store": "^1.3.
|
|
43
|
+
"@t8/store": "^1.3.7"
|
|
49
44
|
}
|
|
50
45
|
}
|
package/dist/index.js
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
// node_modules/@t8/store/src/isStore.ts
|
|
2
|
-
function isStore(x) {
|
|
3
|
-
return x !== null && typeof x === "object" && "onUpdate" in x && typeof x.onUpdate === "function" && "getState" in x && typeof x.getState === "function" && "setState" in x && typeof x.setState === "function";
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
// node_modules/@t8/store/src/isPersistentStore.ts
|
|
7
|
-
function isPersistentStore(x) {
|
|
8
|
-
return isStore(x) && "sync" in x;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// node_modules/@t8/store/src/Store.ts
|
|
12
|
-
var Store = class {
|
|
13
|
-
state;
|
|
14
|
-
callbacks = /* @__PURE__ */ new Set();
|
|
15
|
-
revision = -1;
|
|
16
|
-
constructor(data) {
|
|
17
|
-
this.state = data;
|
|
18
|
-
}
|
|
19
|
-
onUpdate(callback) {
|
|
20
|
-
this.callbacks.add(callback);
|
|
21
|
-
return () => {
|
|
22
|
-
this.callbacks.delete(callback);
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
getState() {
|
|
26
|
-
return this.state;
|
|
27
|
-
}
|
|
28
|
-
setState(update) {
|
|
29
|
-
let prevState = this.state;
|
|
30
|
-
let nextState = update instanceof Function ? update(this.state) : update;
|
|
31
|
-
this.state = nextState;
|
|
32
|
-
this.revision = Math.random();
|
|
33
|
-
for (let callback of this.callbacks) callback(nextState, prevState);
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// node_modules/@t8/store/src/PersistentStore.ts
|
|
38
|
-
function getStorage(session = false) {
|
|
39
|
-
if (typeof window === "undefined") return;
|
|
40
|
-
return session ? sessionStorage : localStorage;
|
|
41
|
-
}
|
|
42
|
-
var PersistentStore = class extends Store {
|
|
43
|
-
storageKey;
|
|
44
|
-
options;
|
|
45
|
-
synced = false;
|
|
46
|
-
/**
|
|
47
|
-
* Creates an instance of the container for data persistent across page
|
|
48
|
-
* reloads.
|
|
49
|
-
*
|
|
50
|
-
* The store data is saved to and restored from the given `storageKey`
|
|
51
|
-
* either of `localStorage` (by default) or `sessionStorage` (if `options.session`
|
|
52
|
-
* is set to `true`). Interaction with the browser storage is skipped in
|
|
53
|
-
* non-browser environments.
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```js
|
|
57
|
-
* let counterStore = new PersistentStore(0, "counter");
|
|
58
|
-
* ```
|
|
59
|
-
*
|
|
60
|
-
* The way data gets saved to and restored from a browser storage entry
|
|
61
|
-
* (including filtering out certain data or otherwise rearranging the
|
|
62
|
-
* saved data) can be overridden by setting `options.serialize` and
|
|
63
|
-
* `options.deserialize`. By default, they are `JSON.stringify()` and
|
|
64
|
-
* `JSON.parse()`.
|
|
65
|
-
*/
|
|
66
|
-
constructor(data, storageKey, options) {
|
|
67
|
-
super(data);
|
|
68
|
-
this.storageKey = storageKey;
|
|
69
|
-
this.options = {
|
|
70
|
-
session: false,
|
|
71
|
-
serialize: (data2) => JSON.stringify(data2),
|
|
72
|
-
deserialize: (content) => JSON.parse(content),
|
|
73
|
-
...options
|
|
74
|
-
};
|
|
75
|
-
this.onUpdate(() => {
|
|
76
|
-
if (this.synced) this.save();
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Saves the store state value to the browser storage.
|
|
81
|
-
*/
|
|
82
|
-
save() {
|
|
83
|
-
let storage = getStorage(this.options?.session);
|
|
84
|
-
let serialize = this.options?.serialize;
|
|
85
|
-
if (this.synced && storage && typeof serialize === "function") {
|
|
86
|
-
try {
|
|
87
|
-
storage.setItem(this.storageKey, serialize(this.state));
|
|
88
|
-
} catch {
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Signals the store to read the state value from the browser storage.
|
|
94
|
-
*/
|
|
95
|
-
sync() {
|
|
96
|
-
let storage = getStorage(this.options?.session);
|
|
97
|
-
let deserialize = this.options?.deserialize;
|
|
98
|
-
let serializedState = null;
|
|
99
|
-
if (storage && typeof deserialize === "function") {
|
|
100
|
-
try {
|
|
101
|
-
serializedState = storage.getItem(this.storageKey);
|
|
102
|
-
if (serializedState !== null)
|
|
103
|
-
this.setState(deserialize(serializedState, this.state));
|
|
104
|
-
} catch {
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (!this.synced) {
|
|
108
|
-
this.synced = true;
|
|
109
|
-
if (serializedState === null) this.save();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Signals the store to read the state value from the browser storage once,
|
|
114
|
-
* disregarding subsequest `syncOnce()` calls.
|
|
115
|
-
*/
|
|
116
|
-
syncOnce() {
|
|
117
|
-
if (!this.synced) this.sync();
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// src/useStore.ts
|
|
122
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
123
|
-
function useStore(store, shouldUpdate = true) {
|
|
124
|
-
if (!isStore(store))
|
|
125
|
-
throw new Error("'store' is not an instance of Store");
|
|
126
|
-
let [, setRevision] = useState(-1);
|
|
127
|
-
let state = store.getState();
|
|
128
|
-
let setState = useMemo(() => store.setState.bind(store), [store]);
|
|
129
|
-
let initialStoreRevision = useRef(store.revision);
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (isPersistentStore(store)) store.syncOnce();
|
|
132
|
-
if (!shouldUpdate) return;
|
|
133
|
-
let unsubscribe = store.onUpdate((nextState, prevState) => {
|
|
134
|
-
if (typeof shouldUpdate !== "function" || shouldUpdate(nextState, prevState))
|
|
135
|
-
setRevision(Math.random());
|
|
136
|
-
});
|
|
137
|
-
if (store.revision !== initialStoreRevision.current)
|
|
138
|
-
setRevision(Math.random());
|
|
139
|
-
return () => {
|
|
140
|
-
unsubscribe();
|
|
141
|
-
initialStoreRevision.current = store.revision;
|
|
142
|
-
};
|
|
143
|
-
}, [store, shouldUpdate]);
|
|
144
|
-
return [state, setState];
|
|
145
|
-
}
|
|
146
|
-
export {
|
|
147
|
-
PersistentStore,
|
|
148
|
-
Store,
|
|
149
|
-
isPersistentStore,
|
|
150
|
-
isStore,
|
|
151
|
-
useStore
|
|
152
|
-
};
|
package/dist/src/useStore.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { type Store } from "@t8/store";
|
|
2
|
-
export type SetStoreState<T> = Store<T>["setState"];
|
|
3
|
-
export type ShouldUpdateCallback<T> = (nextState: T, prevState: T) => boolean;
|
|
4
|
-
export type ShouldUpdate<T> = boolean | ShouldUpdateCallback<T>;
|
|
5
|
-
/**
|
|
6
|
-
* Returns the state value of `store` passed as the parameter and
|
|
7
|
-
* a function to update the store state value.
|
|
8
|
-
*
|
|
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.
|
|
17
|
-
*
|
|
18
|
-
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
19
|
-
* to the store updates. Use case: if the component only requires
|
|
20
|
-
* the store state setter but not the store state value, the
|
|
21
|
-
* component may not need to respond to the store updates at all:
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```js
|
|
25
|
-
* let [, setValue] = useStore(store, false);
|
|
26
|
-
* ```
|
|
27
|
-
*
|
|
28
|
-
* `shouldUpdate` can also be a function `(nextState, prevState) => boolean`
|
|
29
|
-
* to make the component respond only to specific store state changes,
|
|
30
|
-
* when this function returns `true`.
|
|
31
|
-
*/
|
|
32
|
-
export declare function useStore<T>(store: Store<T>, shouldUpdate?: ShouldUpdate<T>): [T, SetStoreState<T>];
|
package/tsconfig.build.json
DELETED