@t8/react-store 1.2.6 → 1.2.8

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 CHANGED
@@ -1,12 +1,10 @@
1
1
  # T8 React Store
2
2
 
3
- *Small React app state management lib aligned with React's state pattern, condensed to the essentials*
3
+ React app state management condensed to the essentials
4
4
 
5
- [![npm](https://img.shields.io/npm/v/@t8/react-store?labelColor=345&color=46e)](https://www.npmjs.com/package/@t8/react-store) ![Lightweight](https://img.shields.io/bundlephobia/minzip/@t8/react-store?label=minzip&labelColor=345&color=46e) ![CSR ✓](https://img.shields.io/badge/CSR-✓-345?labelColor=345) ![SSR ✓](https://img.shields.io/badge/SSR-✓-345?labelColor=345)
5
+ [![npm](https://img.shields.io/npm/v/@t8/react-store?labelColor=345&color=46e)](https://www.npmjs.com/package/@t8/react-store) ![Lightweight](https://img.shields.io/bundlephobia/minzip/@t8/react-store?label=minzip&labelColor=345&color=46e)
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, like Redux Toolkit, Zustand, Jotai, MobX, depart from this picture to varying degrees.
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 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.
7
+ **Features:** Quickest migration from local state · Familiar useState pattern · CSR/SSR without workarounds · Integrable with Immer · Quickly pluggable persistence across page reloads
10
8
 
11
9
  <!-- docsgen-show-start --
12
10
  ```diff
@@ -29,7 +27,7 @@ Installation: `npm i @t8/react-store`
29
27
 
30
28
  ## Shared state setup
31
29
 
32
- Moving the local state to the full-fledged shared state:
30
+ Moving local state to the full-fledged shared state:
33
31
 
34
32
  ```diff
35
33
  + import { Store, useStore } from "@t8/react-store";
@@ -64,29 +62,29 @@ Moving the local state to the full-fledged shared state:
64
62
  [Live counter demo](https://codesandbox.io/p/sandbox/szhdnw?file=%252Fsrc%252FApp.tsx)<br>
65
63
  [Tic-tac-toe](https://codesandbox.io/p/sandbox/tq852v?file=%252Fsrc%252FApp.tsx)
66
64
 
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.
65
+ 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
66
 
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 state updates. The common use case is when a component makes use of the store state setter without using the store state value.
67
+ 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
68
 
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 an example in the [Sharing state via Context](#sharing-state-via-context) section below.
69
+ 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
70
 
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.
71
+ 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
72
 
75
73
  ## Single store or multiple stores
76
74
 
77
75
  An application can have as many stores as needed.
78
76
 
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.
77
+ 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
78
 
81
79
  ## Filtering store updates
82
80
 
83
- When only the store state setter is required, without the store state value, we can opt out from subscription to store state changes by passing `false` as the parameter of `useStore()`:
81
+ 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
82
 
85
83
  ```js
86
- let [, setState] = useState(store, false);
84
+ let [, setValue] = useStore(store, false);
87
85
  ```
88
86
 
89
- Apart from a boolean, `useStore(store, shouldUpdate)` accepts a function of `(nextState, prevState) => boolean` as the optional second parameter to filter store updates to respond to:
87
+ 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
88
 
91
89
  ```jsx
92
90
  import { useStore } from "@t8/react-store";
@@ -134,7 +132,7 @@ Shared state can be provided to the app by means of a regular React Context prov
134
132
 
135
133
  [Live counter demo with Context](https://codesandbox.io/p/sandbox/rtng37?file=%2Fsrc%2FPlusButton.jsx)
136
134
 
137
- 🔹 In a multi-store setup, stores can be located in a single Context or split across multiple Contexts, just like any application data.
135
+ In a multi-store setup, stores can be located in a single Context or split across multiple Contexts, just like any application data.
138
136
 
139
137
  ```jsx
140
138
  import { createContext, useContext } from "react";
@@ -153,11 +151,11 @@ let ItemCard = ({ id }) => {
153
151
  };
154
152
  ```
155
153
 
156
- 🔹 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.
154
+ 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
155
 
158
156
  ## Store data
159
157
 
160
- A store can contain data of any type. With TypeScript, the type of a store containing data of type `T` is `Store<T>` which can be inferred from `data` passed to `new Store(data)`.
158
+ A store can contain data of any kind, whether of a primitive type or nonprimitive.
161
159
 
162
160
  Live demos:<br>
163
161
  [Primitive value state](https://codesandbox.io/p/sandbox/rtng37?file=%2Fsrc%2FPlusButton.jsx)<br>
@@ -171,7 +169,7 @@ Immer can be used with `useStore()` just the same way as [with `useState()`](htt
171
169
 
172
170
  ## Shared loading state
173
171
 
174
- 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.
172
+ 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
173
 
176
174
  ## Persistence across remounts
177
175
 
@@ -179,7 +177,7 @@ A standalone store initialized outside a component can be used by the component
179
177
 
180
178
  ## Persistence across page reloads
181
179
 
182
- Replacing `new Store(data)` with `new PersistentStore(data, storageKey)` as shown below gets the store's state 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.
180
+ 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
181
 
184
182
  ```js
185
183
  import { PersistentStore } from "@t8/react-store";
@@ -187,6 +185,6 @@ import { PersistentStore } from "@t8/react-store";
187
185
  let counterStore = new PersistentStore(0, "counter");
188
186
  ```
189
187
 
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.
188
+ 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
189
 
192
- 🔹 `PersistentStore` skips interaction with the browser storage in non-browser environments, which makes it equally usable with SSR.
190
+ `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 state value of `store` passed as the parameter and
6
- * a function to update the store state value.
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 state setter but not the store state value, the
20
- * component may not need to respond to the store updates at all:
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 `(nextState, prevState) => boolean`
28
- * to make the component respond only to specific store state changes,
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 state = store.getState();
35
- let setState = (0, react.useMemo)(() => store.setState.bind(store), [store]);
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((nextState, prevState) => {
41
- if (typeof shouldUpdate !== "function" || shouldUpdate(nextState, prevState)) setRevision(Math.random());
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 [state, setState];
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 SetStoreState<T> = Store<T>["setState"];
5
- type ShouldUpdateCallback<T> = (nextState: T, prevState: T) => boolean;
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 state value of `store` passed as the parameter and
9
- * a function to update the store state value.
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 state setter but not the store state value, the
23
- * component may not need to respond to the store updates at all:
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 `(nextState, prevState) => boolean`
31
- * to make the component respond only to specific store state changes,
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, SetStoreState<T>];
34
+ declare function useStore<T>(store: Store<T>, shouldUpdate?: ShouldUpdate<T>): [T, SetStoreValue<T>];
35
35
 
36
- export { SetStoreState, ShouldUpdate, ShouldUpdateCallback, useStore };
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 state value of `store` passed as the parameter and
8
- * a function to update the store state value.
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 state setter but not the store state value, the
22
- * component may not need to respond to the store updates at all:
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 `(nextState, prevState) => boolean`
30
- * to make the component respond only to specific store state changes,
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 state = store.getState();
37
- let setState = useMemo(() => store.setState.bind(store), [store]);
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((nextState, prevState) => {
43
- if (typeof shouldUpdate !== "function" || shouldUpdate(nextState, prevState)) setRevision(Math.random());
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 [state, setState];
51
+ return [value, setValue];
52
52
  }
53
53
 
54
54
  export { useStore };
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@t8/react-store",
3
- "version": "1.2.6",
4
- "description": "Small React app state management lib aligned with React's state pattern, condensed to the essentials",
3
+ "version": "1.2.8",
4
+ "description": "React app state management condensed to the essentials",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
8
  "type": "module",
9
9
  "scripts": {
10
- "demo": "npx @t8/serve 3000 * tests/counter -b src/index.tsx",
10
+ "demo": "npx @t8/serve tests/counter --spa -b src/index.tsx",
11
+ "demo-t3": "npx @t8/serve tests/tic-tac-toe --spa -b src/index.tsx",
11
12
  "preversion": "npx npm-run-all shape test",
12
13
  "shape": "npx codeshape",
13
- "test": "npx playwright test --project=chromium",
14
- "tic-tac-toe": "npx @t8/serve 3000 * tests/tic-tac-toe -b src/index.tsx"
14
+ "test": "npx playwright test --project=chromium"
15
15
  },
16
16
  "repository": {
17
17
  "type": "git",
@@ -32,14 +32,14 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@playwright/test": "^1.56.0",
35
- "@t8/serve": "^0.1.38",
35
+ "@t8/serve": "^0.2.0",
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.1"
40
+ "react-dom": "^19.2.3"
41
41
  },
42
42
  "dependencies": {
43
- "@t8/store": "^1.3.8"
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 SetStoreState<T> = Store<T>["setState"];
5
- export type ShouldUpdateCallback<T> = (nextState: T, prevState: T) => boolean;
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 state value of `store` passed as the parameter and
10
- * a function to update the store state value.
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 state setter but not the store state value, the
24
- * component may not need to respond to the store updates at all:
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 `(nextState, prevState) => boolean`
32
- * to make the component respond only to specific store state changes,
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, SetStoreState<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 state = store.getState();
45
- let setState = useMemo(() => store.setState.bind(store), [store]);
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((nextState, prevState) => {
53
+ let unsubscribe = store.onUpdate((nextValue, prevValue) => {
54
54
  if (
55
55
  typeof shouldUpdate !== "function" ||
56
- shouldUpdate(nextState, prevState)
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 [state, setState];
70
+ return [value, setValue];
71
71
  }