@t8/react-store 1.0.22 → 1.0.24

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.
Files changed (3) hide show
  1. package/README.md +54 -30
  2. package/dist/index.js +3 -5
  3. package/package.json +5 -6
package/README.md CHANGED
@@ -16,25 +16,24 @@ Installation: `npm i @t8/react-store`
16
16
  Moving the local state to the full-fledged shared state:
17
17
 
18
18
  ```diff
19
- import { createContext, useContext } from "react";
20
19
  + import { Store, useStore } from "@t8/react-store";
21
20
  +
22
- + let AppContext = createContext(new Store(0));
21
+ + let counterStore = new Store(0);
23
22
 
24
23
  let Counter = () => {
25
24
  - let [counter, setCounter] = useState(0);
26
- + let [counter, setCounter] = useStore(useContext(AppContext));
25
+ + let [counter, setCounter] = useStore(counterStore);
27
26
 
28
27
  let handleClick = () => {
29
28
  setCounter(value => value + 1);
30
29
  };
31
30
 
32
- return <button onClick={handleClick}>{counter}</button>;
31
+ return <button onClick={handleClick}>+ {counter}</button>;
33
32
  };
34
33
 
35
34
  let ResetButton = () => {
36
35
  - let [, setCounter] = useState(0);
37
- + let [, setCounter] = useStore(useContext(AppContext), false);
36
+ + let [, setCounter] = useStore(counterStore, false);
38
37
 
39
38
  let handleClick = () => {
40
39
  setCounter(0);
@@ -46,25 +45,18 @@ Moving the local state to the full-fledged shared state:
46
45
  let App = () => <><Counter/>{" "}<ResetButton/></>;
47
46
  ```
48
47
 
49
- [Live counter demo](https://codesandbox.io/p/sandbox/rtng37?file=%2Fsrc%2FPlusButton.jsx)<br>
48
+ [Live counter demo](https://codesandbox.io/p/sandbox/szhdnw?file=%252Fsrc%252FApp.tsx)<br>
50
49
  [Tic-tac-toe](https://codesandbox.io/p/sandbox/tq852v?file=%252Fsrc%252FApp.tsx)
51
50
 
52
- 🔹 The shared state setup with `@t8/react-store` is very similar to `useState()` allowing for quick migration from local state to shared state or the other way around.
51
+ 🔹 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.
53
52
 
54
53
  🔹 The `false` parameter in `useStore(store, false)` (as in `<ResetButton>` above) tells the hook not to subscribe the component to tracking the store state updates. The common use case is when a component makes use of the store state setter without using the store state value.
55
54
 
56
55
  ## Single store or multiple stores
57
56
 
58
- An application can have as many stores as needed, whether on a single React Context or multiple Contexts.
57
+ An application can have as many stores as needed.
59
58
 
60
- ```js
61
- let AppContext = createContext({
62
- users: new Store(/* ... */),
63
- items: new Store(/* ... */),
64
- });
65
- ```
66
-
67
- 🔹 Splitting data into multiple stores helps maintain more targeted subscriptions to data changes in components.
59
+ 🔹 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.
68
60
 
69
61
  ## Filtering store updates
70
62
 
@@ -74,7 +66,7 @@ When only the store state setter is required, without the store state value, we
74
66
  let [, setState] = useState(store, false);
75
67
  ```
76
68
 
77
- Apart from a boolean, `useStore(store, shouldUpdate)` can take a function of `(nextState, prevState) => boolean` as the second parameter to filter store updates to respond to:
69
+ Apart from a boolean, `useStore(store, shouldUpdate)` accepts a function of `(nextState, prevState) => boolean` as the second parameter to filter store updates to respond to:
78
70
 
79
71
  ```jsx
80
72
  let ItemCard = ({ id }) => {
@@ -84,10 +76,7 @@ let ItemCard = ({ id }) => {
84
76
  return nextItems[id].revision !== prevItems[id].revision;
85
77
  }, [id]);
86
78
 
87
- let [items, setItems] = useStore(
88
- useContext(AppContext).items,
89
- hasRelevantUpdates,
90
- );
79
+ let [items, setItems] = useStore(itemStore, hasRelevantUpdates);
91
80
 
92
81
  return (
93
82
  // Content
@@ -99,15 +88,50 @@ let ItemCard = ({ id }) => {
99
88
 
100
89
  Shared state can be provided to the app by means of a regular React Context provider:
101
90
 
102
- ```diff
103
- let App = () => (
104
- - <AppContext.Provider value={42}>
105
- + <AppContext.Provider value={new Store(42)}>
106
- <PlusButton/>{" "}<Display/>
107
- </AppContext.Provider>
108
- );
91
+ ```ts
92
+ import { createContext } from "react";
93
+
94
+ export let AppContext = createContext(new Store(0));
95
+ ```
96
+
97
+ ```tsx
98
+ let App = () => (
99
+ <AppContext.Provider value={new Store(42)}>
100
+ <PlusButton/>{" "}<Display/>
101
+ </AppContext.Provider>
102
+ );
103
+ ```
104
+
105
+ ```tsx
106
+ let Counter = () => {
107
+ let [counter, setCounter] = useStore(useContext(AppContext));
108
+
109
+ // Rendering
110
+ };
109
111
  ```
110
112
 
113
+ [Live counter demo with Context](https://codesandbox.io/p/sandbox/rtng37?file=%2Fsrc%2FPlusButton.jsx)
114
+
115
+ 🔹 In a multi-store setup, stores can be located in a single Context or split across multiple Contexts, just like any application data.
116
+
117
+ ```js
118
+ // Multiple stores in a single Context
119
+ let AppContext = createContext({
120
+ users: new Store(/* ... */),
121
+ items: new Store(/* ... */),
122
+ });
123
+ ```
124
+
125
+ ```jsx
126
+ let ItemCard = ({ id }) => {
127
+ let [items, setItems] = useStore(useContext(AppContext).items);
128
+
129
+ // Rendering
130
+ };
131
+ ```
132
+
133
+ 🔹 Note that updating the store state 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.
134
+
111
135
  ## Store data
112
136
 
113
137
  A store can contain data of any type.
@@ -126,6 +150,6 @@ Immer can be used with `useStore()` just the same way as [with `useState()`](htt
126
150
 
127
151
  The ready-to-use hook from the [T8 React Pending](https://github.com/t8js/react-pending) package helps manage shared async action state without disturbing the app's state management and actions' code.
128
152
 
129
- ## Standalone store
153
+ ## Remount-persistent state
130
154
 
131
- A store initialized outside a component can be used as the component's remount-persistent state.
155
+ A standalone store initialized outside a component can be used by the component as remount-persistent state, whether used by other components or not.
package/dist/index.js CHANGED
@@ -6,17 +6,15 @@ function isStore(x) {
6
6
  // node_modules/@t8/store/src/Store.ts
7
7
  var Store = class {
8
8
  state;
9
- callbacks = [];
9
+ callbacks = /* @__PURE__ */ new Set();
10
10
  revision = -1;
11
11
  constructor(data) {
12
12
  this.state = data;
13
13
  }
14
14
  onUpdate(callback) {
15
- this.callbacks.push(callback);
15
+ this.callbacks.add(callback);
16
16
  return () => {
17
- for (let i = this.callbacks.length - 1; i >= 0; i--) {
18
- if (this.callbacks[i] === callback) this.callbacks.splice(i, 1);
19
- }
17
+ this.callbacks.delete(callback);
20
18
  };
21
19
  }
22
20
  getState() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t8/react-store",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Concise shared state management for React apps",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -9,8 +9,7 @@
9
9
  "clean": "node -e \"require('node:fs').rmSync('dist', { force: true, recursive: true });\"",
10
10
  "compile": "npx esbuild index.ts --bundle --outdir=dist --platform=neutral --external:react",
11
11
  "demo": "npx @t8/serve 3000 * tests/counter -b src/index.tsx",
12
- "gh-pages": "npx ghstage --theme=t8 --ymid=103784239 --nav=https://raw.githubusercontent.com/t8js/t8js.github.io/refs/heads/main/assets/nav_react.html --single-page",
13
- "prepublishOnly": "npx npm-run-all build gh-pages",
12
+ "prepublishOnly": "npm run build",
14
13
  "preversion": "npx npm-run-all typecheck shape build test",
15
14
  "shape": "npx codeshape",
16
15
  "test": "npx playwright test --project=chromium",
@@ -36,15 +35,15 @@
36
35
  },
37
36
  "devDependencies": {
38
37
  "@playwright/test": "^1.55.1",
39
- "@t8/serve": "^0.1.19",
38
+ "@t8/serve": "^0.1.29",
40
39
  "@types/node": "^24.5.2",
41
40
  "@types/react": "^19.1.10",
42
41
  "@types/react-dom": "^19.1.9",
43
42
  "immer": "^10.1.3",
44
43
  "react-dom": "^19.1.1",
45
- "typescript": "^5.9.2"
44
+ "typescript": "^5.9.3"
46
45
  },
47
46
  "dependencies": {
48
- "@t8/store": "^1.1.3"
47
+ "@t8/store": "^1.1.4"
49
48
  }
50
49
  }