@t8/react-store 1.2.5 → 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 CHANGED
@@ -1,12 +1,12 @@
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
+ A state management lib for React apps aligned with React's state pattern, condensed to the essentials
4
4
 
5
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)
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.
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 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.
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 the local state to the full-fledged shared state:
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
- 🔹 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.
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
- 🔹 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.
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
- 🔹 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.
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
- 🔹 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.
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
- 🔹 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.
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 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()`:
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 [, setState] = useState(store, false);
86
+ let [, setValue] = useStore(store, false);
87
87
  ```
88
88
 
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:
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
- 🔹 In a multi-store setup, stores can be located in a single Context or split across multiple Contexts, just like any application data.
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
- 🔹 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.
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 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)`.
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 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.
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 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.
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
- 🔹 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.
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
- 🔹 `PersistentStore` skips interaction with the browser storage in non-browser environments, which makes it equally usable with SSR.
192
+ `PersistentStore` skips interaction with the browser storage in non-browser environments, which makes it equally usable with SSR.
package/dist/index.cjs CHANGED
@@ -1,10 +1,9 @@
1
1
  let __t8_store = require("@t8/store");
2
2
  let react = require("react");
3
3
 
4
- //#region src/useStore.ts
5
4
  /**
6
- * Returns the state value of `store` passed as the parameter and
7
- * 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.
8
7
  *
9
8
  * @example
10
9
  * ```js
@@ -17,40 +16,39 @@ let react = require("react");
17
16
  *
18
17
  * `shouldUpdate` can be set to `false` to prevent subscription
19
18
  * 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:
19
+ * the store value setter but not the store value, the component
20
+ * may not need to respond to the store updates at all:
22
21
  *
23
22
  * @example
24
23
  * ```js
25
24
  * let [, setValue] = useStore(store, false);
26
25
  * ```
27
26
  *
28
- * `shouldUpdate` can also be a function `(nextState, prevState) => boolean`
29
- * 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,
30
29
  * when this function returns `true`.
31
30
  */
32
31
  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];
32
+ if (!(0, __t8_store.isStore)(store)) throw new Error("'store' is not an instance of Store");
33
+ let [, setRevision] = (0, react.useState)(-1);
34
+ let value = store.getValue();
35
+ let setValue = (0, react.useMemo)(() => store.setValue.bind(store), [store]);
36
+ let initialStoreRevision = (0, react.useRef)(store.revision);
37
+ (0, react.useEffect)(() => {
38
+ if ((0, __t8_store.isPersistentStore)(store)) store.syncOnce();
39
+ if (!shouldUpdate) return;
40
+ let unsubscribe = store.onUpdate((nextValue, prevValue) => {
41
+ if (typeof shouldUpdate !== "function" || shouldUpdate(nextValue, prevValue)) setRevision(Math.random());
42
+ });
43
+ if (store.revision !== initialStoreRevision.current) setRevision(Math.random());
44
+ return () => {
45
+ unsubscribe();
46
+ initialStoreRevision.current = store.revision;
47
+ };
48
+ }, [store, shouldUpdate]);
49
+ return [value, setValue];
51
50
  }
52
51
 
53
- //#endregion
54
52
  exports.useStore = useStore;
55
53
  Object.keys(__t8_store).forEach(function (k) {
56
54
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
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
@@ -3,10 +3,9 @@ import { useEffect, useMemo, useRef, useState } from "react";
3
3
 
4
4
  export * from "@t8/store"
5
5
 
6
- //#region src/useStore.ts
7
6
  /**
8
- * Returns the state value of `store` passed as the parameter and
9
- * 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.
10
9
  *
11
10
  * @example
12
11
  * ```js
@@ -19,38 +18,37 @@ export * from "@t8/store"
19
18
  *
20
19
  * `shouldUpdate` can be set to `false` to prevent subscription
21
20
  * 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:
21
+ * the store value setter but not the store value, the component
22
+ * may not need to respond to the store updates at all:
24
23
  *
25
24
  * @example
26
25
  * ```js
27
26
  * let [, setValue] = useStore(store, false);
28
27
  * ```
29
28
  *
30
- * `shouldUpdate` can also be a function `(nextState, prevState) => boolean`
31
- * 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,
32
31
  * when this function returns `true`.
33
32
  */
34
33
  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];
34
+ if (!isStore(store)) throw new Error("'store' is not an instance of Store");
35
+ let [, setRevision] = useState(-1);
36
+ let value = store.getValue();
37
+ let setValue = useMemo(() => store.setValue.bind(store), [store]);
38
+ let initialStoreRevision = useRef(store.revision);
39
+ useEffect(() => {
40
+ if (isPersistentStore(store)) store.syncOnce();
41
+ if (!shouldUpdate) return;
42
+ let unsubscribe = store.onUpdate((nextValue, prevValue) => {
43
+ if (typeof shouldUpdate !== "function" || shouldUpdate(nextValue, prevValue)) setRevision(Math.random());
44
+ });
45
+ if (store.revision !== initialStoreRevision.current) setRevision(Math.random());
46
+ return () => {
47
+ unsubscribe();
48
+ initialStoreRevision.current = store.revision;
49
+ };
50
+ }, [store, shouldUpdate]);
51
+ return [value, setValue];
53
52
  }
54
53
 
55
- //#endregion
56
- export { useStore };
54
+ export { useStore };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@t8/react-store",
3
- "version": "1.2.5",
4
- "description": "Small React app state management lib aligned with React's state pattern, condensed to the essentials",
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.37",
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.1"
40
+ "react-dom": "^19.2.3"
41
41
  },
42
42
  "dependencies": {
43
- "@t8/store": "^1.3.7"
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
  }