@t8/react-store 1.2.6 → 1.2.7
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 +19 -19
- package/dist/index.cjs +11 -11
- package/dist/index.d.ts +10 -10
- package/dist/index.mjs +11 -11
- package/package.json +5 -5
- package/src/useStore.ts +14 -14
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# T8 React Store
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A state management lib for React apps aligned with React's state pattern, condensed to the essentials
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@t8/react-store)   
|
|
6
6
|
|
|
7
|
-
**Why?** To
|
|
7
|
+
**Why?** To get the shortest migration path from local state to shared state and a quick way to 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
|
-
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
|
|
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 with 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
|
|
|
11
11
|
<!-- docsgen-show-start --
|
|
12
12
|
```diff
|
|
@@ -29,7 +29,7 @@ Installation: `npm i @t8/react-store`
|
|
|
29
29
|
|
|
30
30
|
## Shared state setup
|
|
31
31
|
|
|
32
|
-
Moving
|
|
32
|
+
Moving local state to the full-fledged shared state:
|
|
33
33
|
|
|
34
34
|
```diff
|
|
35
35
|
+ import { Store, useStore } from "@t8/react-store";
|
|
@@ -64,29 +64,29 @@ Moving the local state to the full-fledged shared state:
|
|
|
64
64
|
[Live counter demo](https://codesandbox.io/p/sandbox/szhdnw?file=%252Fsrc%252FApp.tsx)<br>
|
|
65
65
|
[Tic-tac-toe](https://codesandbox.io/p/sandbox/tq852v?file=%252Fsrc%252FApp.tsx)
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
⬥ The shared state setup shown above is very similar to `useState()` allowing for quick migration from local state to shared state or the other way around.
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
⬥ The optional `false` parameter in `useStore(store, false)` (as in `<ResetButton>` above) tells the hook not to subscribe the component to tracking the store updates. The common use case is when a component makes use of the store value setter without using the store value.
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
⬥ With SSR, it's common practice to put shared values into React Context rather than module-level variables to avoid cross-request data sharing. The same applies to stores, see the examples in the [Sharing state via Context](#sharing-state-via-context) section below.
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
⬥ Similarly to instances of the built-in data container classes, such as `Set` and `Map`, stores are created as `new Store(data)` rather than with a factory function.
|
|
74
74
|
|
|
75
75
|
## Single store or multiple stores
|
|
76
76
|
|
|
77
77
|
An application can have as many stores as needed.
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
⬥ Splitting data into multiple stores is one of the strategies to maintain more targeted subscriptions to data changes in components. The other strategy is filtering store updates at the component level, which is discussed below.
|
|
80
80
|
|
|
81
81
|
## Filtering store updates
|
|
82
82
|
|
|
83
|
-
When only the store
|
|
83
|
+
When only the store value setter is required, without the store value, we can opt out from subscription to store changes by passing `false` as the parameter of `useStore()`:
|
|
84
84
|
|
|
85
85
|
```js
|
|
86
|
-
let [,
|
|
86
|
+
let [, setValue] = useStore(store, false);
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
Apart from a boolean, `useStore(store, shouldUpdate)` accepts a function of `(
|
|
89
|
+
Apart from a boolean, `useStore(store, shouldUpdate)` accepts a function of `(nextValue, prevValue) => boolean` as the optional second parameter to filter store value updates to respond to:
|
|
90
90
|
|
|
91
91
|
```jsx
|
|
92
92
|
import { useStore } from "@t8/react-store";
|
|
@@ -134,7 +134,7 @@ Shared state can be provided to the app by means of a regular React Context prov
|
|
|
134
134
|
|
|
135
135
|
[Live counter demo with Context](https://codesandbox.io/p/sandbox/rtng37?file=%2Fsrc%2FPlusButton.jsx)
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
⬥ In a multi-store setup, stores can be located in a single Context or split across multiple Contexts, just like any application data.
|
|
138
138
|
|
|
139
139
|
```jsx
|
|
140
140
|
import { createContext, useContext } from "react";
|
|
@@ -153,11 +153,11 @@ let ItemCard = ({ id }) => {
|
|
|
153
153
|
};
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
⬥ Note that updating the store value doesn't change the store reference sitting in the React Context and therefore doesn't cause updates of the entire Context. Only the components subscribed to updates in the particular store by means of `useStore(store)` will be notified to re-render.
|
|
157
157
|
|
|
158
158
|
## Store data
|
|
159
159
|
|
|
160
|
-
A store can contain data of any
|
|
160
|
+
A store can contain data of any kind, whether of a primitive type or nonprimitive.
|
|
161
161
|
|
|
162
162
|
Live demos:<br>
|
|
163
163
|
[Primitive value state](https://codesandbox.io/p/sandbox/rtng37?file=%2Fsrc%2FPlusButton.jsx)<br>
|
|
@@ -171,7 +171,7 @@ Immer can be used with `useStore()` just the same way as [with `useState()`](htt
|
|
|
171
171
|
|
|
172
172
|
## Shared loading state
|
|
173
173
|
|
|
174
|
-
The ready-to-use hook from
|
|
174
|
+
The ready-to-use hook from [React Pending](https://github.com/t8js/react-pending) helps manage shared async action state without rearranging the app's state management and actions' code.
|
|
175
175
|
|
|
176
176
|
## Persistence across remounts
|
|
177
177
|
|
|
@@ -179,7 +179,7 @@ A standalone store initialized outside a component can be used by the component
|
|
|
179
179
|
|
|
180
180
|
## Persistence across page reloads
|
|
181
181
|
|
|
182
|
-
Replacing `new Store(data)` with `new PersistentStore(data, storageKey)` as shown below gets the store's
|
|
182
|
+
Replacing `new Store(data)` with `new PersistentStore(data, storageKey)` as shown below gets the store's value initially restored from and saved whenever updated to `storageKey` in `localStorage`. (Pass `{ session: true }` as the `options` parameter of `new PersistentStore(data, storageKey, options?)` to use `sessionStorage` instead of `localStorage`.) Otherwise, persistent stores work pretty much like regular stores described above.
|
|
183
183
|
|
|
184
184
|
```js
|
|
185
185
|
import { PersistentStore } from "@t8/react-store";
|
|
@@ -187,6 +187,6 @@ import { PersistentStore } from "@t8/react-store";
|
|
|
187
187
|
let counterStore = new PersistentStore(0, "counter");
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
⬥ The way data gets saved to and restored from a browser storage entry (including filtering out certain data or otherwise rearranging the saved data) can be redefined by setting `options.serialize` and `options.deserialize` in `new PersistentStore(data, storageKey, options?)`. By default, these options act like `JSON.stringify()` and `JSON.parse()` respectively.
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
⬥ `PersistentStore` skips interaction with the browser storage in non-browser environments, which makes it equally usable with SSR.
|
package/dist/index.cjs
CHANGED
|
@@ -2,8 +2,8 @@ let __t8_store = require("@t8/store");
|
|
|
2
2
|
let react = require("react");
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Returns the
|
|
6
|
-
*
|
|
5
|
+
* Returns the value of `store` passed as the parameter and a
|
|
6
|
+
* function to update the store state value.
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* ```js
|
|
@@ -16,29 +16,29 @@ let react = require("react");
|
|
|
16
16
|
*
|
|
17
17
|
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
18
18
|
* to the store updates. Use case: if the component only requires
|
|
19
|
-
* the store
|
|
20
|
-
*
|
|
19
|
+
* the store value setter but not the store value, the component
|
|
20
|
+
* may not need to respond to the store updates at all:
|
|
21
21
|
*
|
|
22
22
|
* @example
|
|
23
23
|
* ```js
|
|
24
24
|
* let [, setValue] = useStore(store, false);
|
|
25
25
|
* ```
|
|
26
26
|
*
|
|
27
|
-
* `shouldUpdate` can also be a function `(
|
|
28
|
-
* to make the component respond only to specific store
|
|
27
|
+
* `shouldUpdate` can also be a function `(nextValue, prevValue) => boolean`
|
|
28
|
+
* to make the component respond only to specific store value changes,
|
|
29
29
|
* when this function returns `true`.
|
|
30
30
|
*/
|
|
31
31
|
function useStore(store, shouldUpdate = true) {
|
|
32
32
|
if (!(0, __t8_store.isStore)(store)) throw new Error("'store' is not an instance of Store");
|
|
33
33
|
let [, setRevision] = (0, react.useState)(-1);
|
|
34
|
-
let
|
|
35
|
-
let
|
|
34
|
+
let value = store.getValue();
|
|
35
|
+
let setValue = (0, react.useMemo)(() => store.setValue.bind(store), [store]);
|
|
36
36
|
let initialStoreRevision = (0, react.useRef)(store.revision);
|
|
37
37
|
(0, react.useEffect)(() => {
|
|
38
38
|
if ((0, __t8_store.isPersistentStore)(store)) store.syncOnce();
|
|
39
39
|
if (!shouldUpdate) return;
|
|
40
|
-
let unsubscribe = store.onUpdate((
|
|
41
|
-
if (typeof shouldUpdate !== "function" || shouldUpdate(
|
|
40
|
+
let unsubscribe = store.onUpdate((nextValue, prevValue) => {
|
|
41
|
+
if (typeof shouldUpdate !== "function" || shouldUpdate(nextValue, prevValue)) setRevision(Math.random());
|
|
42
42
|
});
|
|
43
43
|
if (store.revision !== initialStoreRevision.current) setRevision(Math.random());
|
|
44
44
|
return () => {
|
|
@@ -46,7 +46,7 @@ function useStore(store, shouldUpdate = true) {
|
|
|
46
46
|
initialStoreRevision.current = store.revision;
|
|
47
47
|
};
|
|
48
48
|
}, [store, shouldUpdate]);
|
|
49
|
-
return [
|
|
49
|
+
return [value, setValue];
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
exports.useStore = useStore;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Store } from "@t8/store";
|
|
2
2
|
export * from "@t8/store";
|
|
3
3
|
|
|
4
|
-
type
|
|
5
|
-
type ShouldUpdateCallback<T> = (
|
|
4
|
+
type SetStoreValue<T> = Store<T>["setValue"];
|
|
5
|
+
type ShouldUpdateCallback<T> = (nextValue: T, prevValue: T) => boolean;
|
|
6
6
|
type ShouldUpdate<T> = boolean | ShouldUpdateCallback<T>;
|
|
7
7
|
/**
|
|
8
|
-
* Returns the
|
|
9
|
-
*
|
|
8
|
+
* Returns the value of `store` passed as the parameter and a
|
|
9
|
+
* function to update the store state value.
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
12
|
* ```js
|
|
@@ -19,18 +19,18 @@ type ShouldUpdate<T> = boolean | ShouldUpdateCallback<T>;
|
|
|
19
19
|
*
|
|
20
20
|
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
21
21
|
* to the store updates. Use case: if the component only requires
|
|
22
|
-
* the store
|
|
23
|
-
*
|
|
22
|
+
* the store value setter but not the store value, the component
|
|
23
|
+
* may not need to respond to the store updates at all:
|
|
24
24
|
*
|
|
25
25
|
* @example
|
|
26
26
|
* ```js
|
|
27
27
|
* let [, setValue] = useStore(store, false);
|
|
28
28
|
* ```
|
|
29
29
|
*
|
|
30
|
-
* `shouldUpdate` can also be a function `(
|
|
31
|
-
* to make the component respond only to specific store
|
|
30
|
+
* `shouldUpdate` can also be a function `(nextValue, prevValue) => boolean`
|
|
31
|
+
* to make the component respond only to specific store value changes,
|
|
32
32
|
* when this function returns `true`.
|
|
33
33
|
*/
|
|
34
|
-
declare function useStore<T>(store: Store<T>, shouldUpdate?: ShouldUpdate<T>): [T,
|
|
34
|
+
declare function useStore<T>(store: Store<T>, shouldUpdate?: ShouldUpdate<T>): [T, SetStoreValue<T>];
|
|
35
35
|
|
|
36
|
-
export {
|
|
36
|
+
export { SetStoreValue, ShouldUpdate, ShouldUpdateCallback, useStore };
|
package/dist/index.mjs
CHANGED
|
@@ -4,8 +4,8 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
|
|
4
4
|
export * from "@t8/store"
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Returns the
|
|
8
|
-
*
|
|
7
|
+
* Returns the value of `store` passed as the parameter and a
|
|
8
|
+
* function to update the store state value.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```js
|
|
@@ -18,29 +18,29 @@ export * from "@t8/store"
|
|
|
18
18
|
*
|
|
19
19
|
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
20
20
|
* to the store updates. Use case: if the component only requires
|
|
21
|
-
* the store
|
|
22
|
-
*
|
|
21
|
+
* the store value setter but not the store value, the component
|
|
22
|
+
* may not need to respond to the store updates at all:
|
|
23
23
|
*
|
|
24
24
|
* @example
|
|
25
25
|
* ```js
|
|
26
26
|
* let [, setValue] = useStore(store, false);
|
|
27
27
|
* ```
|
|
28
28
|
*
|
|
29
|
-
* `shouldUpdate` can also be a function `(
|
|
30
|
-
* to make the component respond only to specific store
|
|
29
|
+
* `shouldUpdate` can also be a function `(nextValue, prevValue) => boolean`
|
|
30
|
+
* to make the component respond only to specific store value changes,
|
|
31
31
|
* when this function returns `true`.
|
|
32
32
|
*/
|
|
33
33
|
function useStore(store, shouldUpdate = true) {
|
|
34
34
|
if (!isStore(store)) throw new Error("'store' is not an instance of Store");
|
|
35
35
|
let [, setRevision] = useState(-1);
|
|
36
|
-
let
|
|
37
|
-
let
|
|
36
|
+
let value = store.getValue();
|
|
37
|
+
let setValue = useMemo(() => store.setValue.bind(store), [store]);
|
|
38
38
|
let initialStoreRevision = useRef(store.revision);
|
|
39
39
|
useEffect(() => {
|
|
40
40
|
if (isPersistentStore(store)) store.syncOnce();
|
|
41
41
|
if (!shouldUpdate) return;
|
|
42
|
-
let unsubscribe = store.onUpdate((
|
|
43
|
-
if (typeof shouldUpdate !== "function" || shouldUpdate(
|
|
42
|
+
let unsubscribe = store.onUpdate((nextValue, prevValue) => {
|
|
43
|
+
if (typeof shouldUpdate !== "function" || shouldUpdate(nextValue, prevValue)) setRevision(Math.random());
|
|
44
44
|
});
|
|
45
45
|
if (store.revision !== initialStoreRevision.current) setRevision(Math.random());
|
|
46
46
|
return () => {
|
|
@@ -48,7 +48,7 @@ function useStore(store, shouldUpdate = true) {
|
|
|
48
48
|
initialStoreRevision.current = store.revision;
|
|
49
49
|
};
|
|
50
50
|
}, [store, shouldUpdate]);
|
|
51
|
-
return [
|
|
51
|
+
return [value, setValue];
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export { useStore };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t8/react-store",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.7",
|
|
4
|
+
"description": "A state management lib for React apps aligned with React's state pattern, condensed to the essentials",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@playwright/test": "^1.56.0",
|
|
35
|
-
"@t8/serve": "^0.1.
|
|
35
|
+
"@t8/serve": "^0.1.39",
|
|
36
36
|
"@types/node": "^24.5.2",
|
|
37
37
|
"@types/react": "^19.2.7",
|
|
38
38
|
"@types/react-dom": "^19.2.3",
|
|
39
39
|
"immer": "^11.0.1",
|
|
40
|
-
"react-dom": "^19.2.
|
|
40
|
+
"react-dom": "^19.2.3"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@t8/store": "^1.
|
|
43
|
+
"@t8/store": "^1.4.0"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/src/useStore.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { isPersistentStore, isStore, type Store } from "@t8/store";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
|
|
4
|
-
export type
|
|
5
|
-
export type ShouldUpdateCallback<T> = (
|
|
4
|
+
export type SetStoreValue<T> = Store<T>["setValue"];
|
|
5
|
+
export type ShouldUpdateCallback<T> = (nextValue: T, prevValue: T) => boolean;
|
|
6
6
|
export type ShouldUpdate<T> = boolean | ShouldUpdateCallback<T>;
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Returns the
|
|
10
|
-
*
|
|
9
|
+
* Returns the value of `store` passed as the parameter and a
|
|
10
|
+
* function to update the store state value.
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```js
|
|
@@ -20,29 +20,29 @@ export type ShouldUpdate<T> = boolean | ShouldUpdateCallback<T>;
|
|
|
20
20
|
*
|
|
21
21
|
* `shouldUpdate` can be set to `false` to prevent subscription
|
|
22
22
|
* to the store updates. Use case: if the component only requires
|
|
23
|
-
* the store
|
|
24
|
-
*
|
|
23
|
+
* the store value setter but not the store value, the component
|
|
24
|
+
* may not need to respond to the store updates at all:
|
|
25
25
|
*
|
|
26
26
|
* @example
|
|
27
27
|
* ```js
|
|
28
28
|
* let [, setValue] = useStore(store, false);
|
|
29
29
|
* ```
|
|
30
30
|
*
|
|
31
|
-
* `shouldUpdate` can also be a function `(
|
|
32
|
-
* to make the component respond only to specific store
|
|
31
|
+
* `shouldUpdate` can also be a function `(nextValue, prevValue) => boolean`
|
|
32
|
+
* to make the component respond only to specific store value changes,
|
|
33
33
|
* when this function returns `true`.
|
|
34
34
|
*/
|
|
35
35
|
export function useStore<T>(
|
|
36
36
|
store: Store<T>,
|
|
37
37
|
shouldUpdate: ShouldUpdate<T> = true,
|
|
38
|
-
): [T,
|
|
38
|
+
): [T, SetStoreValue<T>] {
|
|
39
39
|
if (!isStore<T>(store))
|
|
40
40
|
throw new Error("'store' is not an instance of Store");
|
|
41
41
|
|
|
42
42
|
let [, setRevision] = useState(-1);
|
|
43
43
|
|
|
44
|
-
let
|
|
45
|
-
let
|
|
44
|
+
let value = store.getValue();
|
|
45
|
+
let setValue = useMemo(() => store.setValue.bind(store), [store]);
|
|
46
46
|
let initialStoreRevision = useRef(store.revision);
|
|
47
47
|
|
|
48
48
|
useEffect(() => {
|
|
@@ -50,10 +50,10 @@ export function useStore<T>(
|
|
|
50
50
|
|
|
51
51
|
if (!shouldUpdate) return;
|
|
52
52
|
|
|
53
|
-
let unsubscribe = store.onUpdate((
|
|
53
|
+
let unsubscribe = store.onUpdate((nextValue, prevValue) => {
|
|
54
54
|
if (
|
|
55
55
|
typeof shouldUpdate !== "function" ||
|
|
56
|
-
shouldUpdate(
|
|
56
|
+
shouldUpdate(nextValue, prevValue)
|
|
57
57
|
)
|
|
58
58
|
setRevision(Math.random());
|
|
59
59
|
});
|
|
@@ -67,5 +67,5 @@ export function useStore<T>(
|
|
|
67
67
|
};
|
|
68
68
|
}, [store, shouldUpdate]);
|
|
69
69
|
|
|
70
|
-
return [
|
|
70
|
+
return [value, setValue];
|
|
71
71
|
}
|