@t8/react-store 1.0.12 → 1.0.14

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
@@ -16,34 +16,34 @@ 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
- + import {Store, useStore} from '@t8/react-store';
19
+ import { createContext, useContext } from "react";
20
+ + import { Store, useStore } from "@t8/react-store";
21
21
  +
22
22
  + let AppContext = createContext(new Store(0));
23
23
 
24
24
  let Counter = () => {
25
- - let [counter, setCounter] = useState(0);
26
- + let [counter, setCounter] = useStore(useContext(AppContext));
25
+ - let [counter, setCounter] = useState(0);
26
+ + let [counter, setCounter] = useStore(useContext(AppContext));
27
27
 
28
- let handleClick = () => {
29
- setCounter(value => value + 1);
30
- };
28
+ let handleClick = () => {
29
+ setCounter(value => value + 1);
30
+ };
31
31
 
32
- return <button onClick={handleClick}>{counter}</button>;
32
+ return <button onClick={handleClick}>{counter}</button>;
33
33
  };
34
34
 
35
35
  let ResetButton = () => {
36
- - let [, setCounter] = useState(0);
37
- + let [, setCounter] = useStore(useContext(AppContext), false);
36
+ - let [, setCounter] = useState(0);
37
+ + let [, setCounter] = useStore(useContext(AppContext), false);
38
38
 
39
- let handleClick = () => {
40
- setCounter(0);
41
- };
39
+ let handleClick = () => {
40
+ setCounter(0);
41
+ };
42
42
 
43
- return <button onClick={handleClick}>×</button>;
43
+ return <button onClick={handleClick}>×</button>;
44
44
  };
45
45
 
46
- let App = () => <><Counter/>{' '}<ResetButton/></>;
46
+ let App = () => <><Counter/>{" "}<ResetButton/></>;
47
47
  ```
48
48
 
49
49
  [Live demo](https://codesandbox.io/p/sandbox/rtng37?file=%2Fsrc%2FPlusButton.jsx)
@@ -56,27 +56,27 @@ Moving the local state to the full-fledged shared state:
56
56
 
57
57
  ```js
58
58
  let AppContext = createContext({
59
- users: new Store(/* ... */),
60
- items: new Store(/* ... */),
59
+ users: new Store(/* ... */),
60
+ items: new Store(/* ... */),
61
61
  });
62
62
  ```
63
63
 
64
64
  🔹 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:
65
65
 
66
66
  ```jsx
67
- let ItemCard = ({id}) => {
68
- let hasRelevantUpdates = useCallback((nextItems, prevItems) => {
69
- return nextItems[id].revision !== prevItems[id].revision;
70
- }, [id]);
71
-
72
- let [items, setItems] = useStore(
73
- useContext(AppContext).items,
74
- hasRelevantUpdates,
75
- );
76
-
77
- return (
78
- // content
79
- );
67
+ let ItemCard = ({ id }) => {
68
+ let hasRelevantUpdates = useCallback((nextItems, prevItems) => {
69
+ return nextItems[id].revision !== prevItems[id].revision;
70
+ }, [id]);
71
+
72
+ let [items, setItems] = useStore(
73
+ useContext(AppContext).items,
74
+ hasRelevantUpdates,
75
+ );
76
+
77
+ return (
78
+ // content
79
+ );
80
80
  };
81
81
  ```
82
82
 
@@ -84,10 +84,10 @@ let ItemCard = ({id}) => {
84
84
 
85
85
  ```diff
86
86
  let App = () => (
87
- - <AppContext.Provider value={42}>
88
- + <AppContext.Provider value={new Store(42)}>
89
- <PlusButton/>{' '}<Display/>
90
- </AppContext.Provider>
87
+ - <AppContext.Provider value={42}>
88
+ + <AppContext.Provider value={new Store(42)}>
89
+ <PlusButton/>{" "}<Display/>
90
+ </AppContext.Provider>
91
91
  );
92
92
  ```
93
93
 
package/index.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from '@t8/store';
2
- export * from './src/useStore';
1
+ export * from "@t8/store";
2
+ export * from "./src/useStore";
package/package.json CHANGED
@@ -1,39 +1,49 @@
1
- {
2
- "name": "@t8/react-store",
3
- "version": "1.0.12",
4
- "description": "Straightforward and minimalist shared state management for React apps",
5
- "main": "dist/index.js",
6
- "type": "module",
7
- "scripts": {
8
- "build": "npx npm-run-all clean compile",
9
- "clean": "node -e \"require('node:fs').rmSync('dist', {force: true, recursive: true});\"",
10
- "compile": "npx esbuild index.ts --bundle --outdir=dist --platform=neutral --external:react",
11
- "gh-pages": "npx ghstage --theme=t8 --ymid=103784239 --nav=https://raw.githubusercontent.com/t8js/t8js.github.io/refs/heads/main/assets/nav.html",
12
- "prepublishOnly": "npx npm-run-all build gh-pages",
13
- "preversion": "npx npm-run-all typecheck shape build",
14
- "shape": "npx codeshape",
15
- "typecheck": "tsc --noEmit"
16
- },
17
- "repository": {
18
- "type": "git",
19
- "url": "git+https://github.com/t8js/react-store.git"
20
- },
21
- "homepage": "https://t8.js.org/react-store",
22
- "keywords": [
23
- "react",
24
- "shared state",
25
- "store"
26
- ],
27
- "author": "axtk",
28
- "license": "ISC",
29
- "peerDependencies": {
30
- "react": ">=16.8"
31
- },
32
- "devDependencies": {
33
- "@types/react": "^19.1.10",
34
- "typescript": "^5.9.2"
35
- },
36
- "dependencies": {
37
- "@t8/store": "^1.1.2"
38
- }
39
- }
1
+ {
2
+ "name": "@t8/react-store",
3
+ "version": "1.0.14",
4
+ "description": "Straightforward and minimalist shared state management for React apps",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "npx npm-run-all clean compile",
9
+ "clean": "node -e \"require('node:fs').rmSync('dist', {force: true, recursive: true});\"",
10
+ "compile": "npx esbuild index.ts --bundle --outdir=dist --platform=neutral --external:react",
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.html",
13
+ "prepublishOnly": "npx npm-run-all build gh-pages",
14
+ "preversion": "npx npm-run-all typecheck shape build test",
15
+ "shape": "npx codeshape",
16
+ "test": "npx playwright test --project=chromium",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/t8js/react-store.git"
22
+ },
23
+ "homepage": "https://t8.js.org/react-store",
24
+ "keywords": [
25
+ "react",
26
+ "state management",
27
+ "shared state",
28
+ "global state",
29
+ "store"
30
+ ],
31
+ "author": "axtk",
32
+ "license": "ISC",
33
+ "peerDependencies": {
34
+ "react": ">=16.8"
35
+ },
36
+ "devDependencies": {
37
+ "@playwright/test": "^1.55.1",
38
+ "@t8/serve": "^0.1.19",
39
+ "@types/node": "^24.5.2",
40
+ "@types/react": "^19.1.10",
41
+ "@types/react-dom": "^19.1.9",
42
+ "immer": "^10.1.3",
43
+ "react-dom": "^19.1.1",
44
+ "typescript": "^5.9.2"
45
+ },
46
+ "dependencies": {
47
+ "@t8/store": "^1.1.3"
48
+ }
49
+ }
package/src/useStore.ts CHANGED
@@ -1,66 +1,66 @@
1
- import {isStore, type Store} from '@t8/store';
2
- import {useEffect, useMemo, useRef, useState} from 'react';
1
+ import { isStore, type Store } from "@t8/store";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
3
 
4
- export type SetStoreState<T> = Store<T>['setState'];
4
+ export type SetStoreState<T> = Store<T>["setState"];
5
5
  export type ShouldUpdateCallback<T> = (nextState: T, prevState: T) => boolean;
6
6
  export type ShouldUpdate<T> = boolean | ShouldUpdateCallback<T>;
7
7
 
8
8
  export function useStore<T>(
9
- store: Store<T>,
10
- /**
11
- * Controls whether the component should be updated in response
12
- * to the store updates.
13
- *
14
- * @defaultValue `true`
15
- *
16
- * Can be set to `false` when the component only requires the
17
- * store state setter but not the state value itself, and so the
18
- * component doesn't need to respond to updates in the store state.
19
- *
20
- * ```ts
21
- * let [, setValue] = useStore(store, false);
22
- * ```
23
- *
24
- * Can be set to a function `(nextState, prevState) => boolean` to
25
- * make the component respond only to specific store state changes.
26
- */
27
- shouldUpdate: ShouldUpdate<T> = true,
9
+ store: Store<T>,
10
+ /**
11
+ * Controls whether the component should be updated in response
12
+ * to the store updates.
13
+ *
14
+ * @defaultValue `true`
15
+ *
16
+ * Can be set to `false` when the component only requires the
17
+ * store state setter but not the state value itself, and so the
18
+ * component doesn't need to respond to updates in the store state.
19
+ *
20
+ * ```ts
21
+ * let [, setValue] = useStore(store, false);
22
+ * ```
23
+ *
24
+ * Can be set to a function `(nextState, prevState) => boolean` to
25
+ * make the component respond only to specific store state changes.
26
+ */
27
+ shouldUpdate: ShouldUpdate<T> = true,
28
28
  ): [T, SetStoreState<T>] {
29
- if (!isStore(store)) throw new Error("'store' is not an instance of Store");
29
+ if (!isStore(store)) throw new Error("'store' is not an instance of Store");
30
30
 
31
- let [, setRevision] = useState(-1);
32
- let initedRef = useRef(false);
31
+ let [, setRevision] = useState(-1);
32
+ let initedRef = useRef(false);
33
33
 
34
- let state = store.getState();
35
- let setState = useMemo(() => store.setState.bind(store), [store]);
36
- let initialStoreRevision = useRef(store.revision);
34
+ let state = store.getState();
35
+ let setState = useMemo(() => store.setState.bind(store), [store]);
36
+ let initialStoreRevision = useRef(store.revision);
37
37
 
38
- useEffect(() => {
39
- if (!shouldUpdate) return;
38
+ useEffect(() => {
39
+ if (!shouldUpdate) return;
40
40
 
41
- let unsubscribe = store.onUpdate((nextState, prevState) => {
42
- if (
43
- typeof shouldUpdate !== 'function' ||
44
- shouldUpdate(nextState, prevState)
45
- )
46
- setRevision(Math.random());
47
- });
41
+ let unsubscribe = store.onUpdate((nextState, prevState) => {
42
+ if (
43
+ typeof shouldUpdate !== "function" ||
44
+ shouldUpdate(nextState, prevState)
45
+ )
46
+ setRevision(Math.random());
47
+ });
48
48
 
49
- // Trigger a rerender if the store state changed after the component
50
- // last read it and before the component subscribed to its changes.
51
- if (!initedRef.current) {
52
- initedRef.current = true;
49
+ // Trigger a rerender if the store state changed after the component
50
+ // last read it and before the component subscribed to its changes.
51
+ if (!initedRef.current) {
52
+ initedRef.current = true;
53
53
 
54
- if (store.revision !== initialStoreRevision.current)
55
- setRevision(Math.random());
56
- }
54
+ if (store.revision !== initialStoreRevision.current)
55
+ setRevision(Math.random());
56
+ }
57
57
 
58
- return () => {
59
- unsubscribe();
60
- initedRef.current = false;
61
- initialStoreRevision.current = store.revision;
62
- };
63
- }, [store, shouldUpdate]);
58
+ return () => {
59
+ unsubscribe();
60
+ initedRef.current = false;
61
+ initialStoreRevision.current = store.revision;
62
+ };
63
+ }, [store, shouldUpdate]);
64
64
 
65
- return [state, setState];
65
+ return [state, setState];
66
66
  }
package/tsconfig.json CHANGED
@@ -1,12 +1,13 @@
1
- {
2
- "include": ["index.ts", "src/**/*"],
3
- "compilerOptions": {
4
- "lib": ["ESNext", "DOM"],
5
- "target": "ESNext",
6
- "outDir": "dist",
7
- "moduleResolution": "node",
8
- "strict": true,
9
- "noUnusedLocals": true,
10
- "noUnusedParameters": true
11
- }
12
- }
1
+ {
2
+ "include": ["index.ts", "playwright.config.ts", "src", "tests"],
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "lib": ["ESNext", "DOM"],
6
+ "target": "ESNext",
7
+ "outDir": "dist",
8
+ "moduleResolution": "node",
9
+ "strict": true,
10
+ "noUnusedLocals": true,
11
+ "noUnusedParameters": true
12
+ }
13
+ }