@view-models/react 1.1.0 → 1.3.0
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 +9 -23
- package/dist/index.d.ts +90 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -0
- package/package.json +12 -4
- package/src/useModelState.ts +62 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/useModelState.d.ts +0 -32
- package/dist/useModelState.d.ts.map +0 -1
- package/dist/useModelState.js +0 -33
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# @view-models/react
|
|
2
2
|
|
|
3
|
+
[](https://github.com/sunesimonsen/view-models-react/actions/workflows/ci.yml)
|
|
4
|
+
[](https://unpkg.com/@view-models/react@latest/dist/index.js)
|
|
5
|
+
|
|
3
6
|
React integration for [@view-models/core](https://github.com/sunesimonsen/view-models-core).
|
|
4
7
|
|
|
5
8
|
## Installation
|
|
@@ -20,11 +23,11 @@ type CounterState = Readonly<{ count: number }>;
|
|
|
20
23
|
|
|
21
24
|
class CounterViewModel extends ViewModel<CounterState> {
|
|
22
25
|
increment = () => {
|
|
23
|
-
|
|
26
|
+
super.update({ count: super.state.count + 1 });
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
decrement = () => {
|
|
27
|
-
|
|
30
|
+
super.update({ count: super.state.count - 1 });
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
|
|
@@ -58,11 +61,11 @@ type CounterState = Readonly<{ count: number }>;
|
|
|
58
61
|
|
|
59
62
|
class CounterViewModel extends ViewModel<CounterState> {
|
|
60
63
|
increment = () => {
|
|
61
|
-
|
|
64
|
+
super.update({ count: super.state.count + 1 });
|
|
62
65
|
};
|
|
63
66
|
|
|
64
67
|
decrement = () => {
|
|
65
|
-
|
|
68
|
+
super.update({ count: super.state.count - 1 });
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -86,26 +89,9 @@ function Counter() {
|
|
|
86
89
|
}
|
|
87
90
|
```
|
|
88
91
|
|
|
89
|
-
## API
|
|
90
|
-
|
|
91
|
-
### `useModelState<T>(model: ViewModel<T>): T`
|
|
92
|
-
|
|
93
|
-
A React hook that subscribes to state updates from a ViewModel instance.
|
|
94
|
-
|
|
95
|
-
**Parameters:**
|
|
96
|
-
|
|
97
|
-
- `model`: A ViewModel instance to subscribe to
|
|
98
|
-
|
|
99
|
-
**Returns:**
|
|
100
|
-
|
|
101
|
-
- The current state of the ViewModel
|
|
102
|
-
|
|
103
|
-
**Features:**
|
|
92
|
+
## API Reference
|
|
104
93
|
|
|
105
|
-
|
|
106
|
-
- Automatically unsubscribes when the component unmounts
|
|
107
|
-
- Re-renders the component when the ViewModel state changes
|
|
108
|
-
- Multiple components can subscribe to the same ViewModel
|
|
94
|
+
For detailed API documentation, see [docs](./docs).
|
|
109
95
|
|
|
110
96
|
## License
|
|
111
97
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Function that gets called when the state changes.
|
|
3
|
+
*
|
|
4
|
+
* @template T - The state type
|
|
5
|
+
* @param state - The new state
|
|
6
|
+
*/
|
|
7
|
+
type ViewModelListener = () => void;
|
|
8
|
+
/**
|
|
9
|
+
* Interface for a ViewModel that manages state and notifies subscribers of changes.
|
|
10
|
+
*
|
|
11
|
+
* ViewModels provide a way to manage component state outside of the component tree,
|
|
12
|
+
* allowing multiple components to share and react to the same state. They follow
|
|
13
|
+
* the observer pattern, where components can subscribe to state changes and receive
|
|
14
|
+
* notifications when updates occur.
|
|
15
|
+
*
|
|
16
|
+
* @template T - The type of state managed by this ViewModel
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Implementing a ViewModel
|
|
21
|
+
* class CounterViewModel extends ViewModel<{ count: number }> {
|
|
22
|
+
* increment = () => super.update({ count: super.state.count + 1 });
|
|
23
|
+
* decrement = () => super.update(({ count: super.state.count - 1 }));
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* // Using in a component
|
|
27
|
+
* const counterModel = new CounterViewModel({ count: 0 });
|
|
28
|
+
* const { count } = useModelState(counterModel);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
interface ViewModel<T> {
|
|
32
|
+
/**
|
|
33
|
+
* Subscribe to state changes.
|
|
34
|
+
*
|
|
35
|
+
* The listener will be called immediately after any state update.
|
|
36
|
+
*
|
|
37
|
+
* @param listener - Function to call when state changes
|
|
38
|
+
* @returns Function to unsubscribe the listener
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const unsubscribe = viewModel.subscribe((state) => {
|
|
43
|
+
* console.log('State changed:', state);
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // Later, when you want to stop listening:
|
|
47
|
+
* unsubscribe();
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
subscribe(listener: ViewModelListener): () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Get the current state.
|
|
53
|
+
*
|
|
54
|
+
* @returns The current state
|
|
55
|
+
*/
|
|
56
|
+
get state(): T;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A React hook that subscribes a component to a ViewModel's state updates.
|
|
60
|
+
*
|
|
61
|
+
* This hook connects a React component to a ViewModel instance, automatically
|
|
62
|
+
* subscribing to state changes and triggering re-renders when the state updates.
|
|
63
|
+
*
|
|
64
|
+
* @template T - The state type, which must extend the State interface
|
|
65
|
+
* @param model - The ViewModel instance to subscribe to
|
|
66
|
+
* @returns The current state from the ViewModel
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* class CounterViewModel extends ViewModel<{ count: number }> {
|
|
71
|
+
* increment = () => super.update({ count: super.state.count + 1 });
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* function Counter() {
|
|
75
|
+
* const counterModel = useMemo(() => new CounterViewModel({ count: 0 }), []);
|
|
76
|
+
* const { count } = useModelState(counterModel);
|
|
77
|
+
*
|
|
78
|
+
* return (
|
|
79
|
+
* <div>
|
|
80
|
+
* <p>Count: {count}</p>
|
|
81
|
+
* <button onClick={counterModel.increment}>+</button>
|
|
82
|
+
* </div>
|
|
83
|
+
* );
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
declare function useModelState<T>(model: ViewModel<T>): T;
|
|
88
|
+
|
|
89
|
+
export { useModelState };
|
|
90
|
+
export type { ViewModel, ViewModelListener };
|
package/dist/index.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import{useSyncExternalStore as r}from"react";function t(t){return r(t.subscribe.bind(t),()=>t.state)}export{t as useModelState};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/useModelState.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\n\n/**\n * Function that gets called when the state changes.\n *\n * @template T - The state type\n * @param state - The new state\n */\nexport type ViewModelListener = () => void;\n\n/**\n * Interface for a ViewModel that manages state and notifies subscribers of changes.\n *\n * ViewModels provide a way to manage component state outside of the component tree,\n * allowing multiple components to share and react to the same state. They follow\n * the observer pattern, where components can subscribe to state changes and receive\n * notifications when updates occur.\n *\n * @template T - The type of state managed by this ViewModel\n *\n * @example\n * ```typescript\n * // Implementing a ViewModel\n * class CounterViewModel extends ViewModel<{ count: number }> {\n * increment = () => super.update({ count: super.state.count + 1 });\n * decrement = () => super.update(({ count: super.state.count - 1 }));\n * }\n *\n * // Using in a component\n * const counterModel = new CounterViewModel({ count: 0 });\n * const { count } = useModelState(counterModel);\n * ```\n */\nexport interface ViewModel<T> {\n /**\n * Subscribe to state changes.\n *\n * The listener will be called immediately after any state update.\n *\n * @param listener - Function to call when state changes\n * @returns Function to unsubscribe the listener\n *\n * @example\n * ```typescript\n * const unsubscribe = viewModel.subscribe((state) => {\n * console.log('State changed:', state);\n * });\n *\n * // Later, when you want to stop listening:\n * unsubscribe();\n * ```\n */\n subscribe(listener: ViewModelListener): () => void;\n\n /**\n * Get the current state.\n *\n * @returns The current state\n */\n get state(): T;\n}\n\n/**\n * A React hook that subscribes a component to a ViewModel's state updates.\n *\n * This hook connects a React component to a ViewModel instance, automatically\n * subscribing to state changes and triggering re-renders when the state updates.\n *\n * @template T - The state type, which must extend the State interface\n * @param model - The ViewModel instance to subscribe to\n * @returns The current state from the ViewModel\n *\n * @example\n * ```tsx\n * class CounterViewModel extends ViewModel<{ count: number }> {\n * increment = () => super.update({ count: super.state.count + 1 });\n * }\n *\n * function Counter() {\n * const counterModel = useMemo(() => new CounterViewModel({ count: 0 }), []);\n * const { count } = useModelState(counterModel);\n *\n * return (\n * <div>\n * <p>Count: {count}</p>\n * <button onClick={counterModel.increment}>+</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useModelState<T>(model: ViewModel<T>): T {\n return useSyncExternalStore(model.subscribe.bind(model), () => model.state);\n}\n"],"names":["useModelState","model","useSyncExternalStore","subscribe","bind","state"],"mappings":"6CA2FM,SAAUA,EAAiBC,GAC/B,OAAOC,EAAqBD,EAAME,UAAUC,KAAKH,GAAQ,IAAMA,EAAMI,MACvE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@view-models/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "React integration for @view-models/core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,13 +16,15 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"build": "npm run clean && rollup -c",
|
|
20
21
|
"typecheck": "tsc --noEmit",
|
|
21
22
|
"test": "vitest run",
|
|
22
23
|
"test:watch": "vitest",
|
|
23
24
|
"test:coverage": "vitest run --coverage",
|
|
24
25
|
"format": "prettier --write .",
|
|
25
26
|
"format:check": "prettier --check .",
|
|
27
|
+
"docs": "typedoc && npm run format",
|
|
26
28
|
"prepublishOnly": "npm run build"
|
|
27
29
|
},
|
|
28
30
|
"keywords": [
|
|
@@ -45,18 +47,24 @@
|
|
|
45
47
|
"node": ">=18.0.0"
|
|
46
48
|
},
|
|
47
49
|
"peerDependencies": {
|
|
48
|
-
"@view-models/core": "^1.1.0",
|
|
49
50
|
"react": ">=17.0.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
53
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
54
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
52
55
|
"@testing-library/react": "^16.1.0",
|
|
53
56
|
"@types/react": "^18.3.18",
|
|
54
|
-
"@view-models/core": "^
|
|
57
|
+
"@view-models/core": "^3.0.0",
|
|
55
58
|
"@vitest/coverage-v8": "^2.1.8",
|
|
56
59
|
"jsdom": "^26.0.0",
|
|
57
60
|
"prettier": "^3.4.2",
|
|
58
61
|
"react": "^18.3.1",
|
|
59
62
|
"react-dom": "^18.3.1",
|
|
63
|
+
"rollup": "^4.57.0",
|
|
64
|
+
"rollup-plugin-dts": "^6.3.0",
|
|
65
|
+
"tslib": "^2.8.1",
|
|
66
|
+
"typedoc": "^0.28.16",
|
|
67
|
+
"typedoc-plugin-markdown": "^4.9.0",
|
|
60
68
|
"typescript": "^5.7.3",
|
|
61
69
|
"vitest": "^2.1.8"
|
|
62
70
|
},
|
package/src/useModelState.ts
CHANGED
|
@@ -1,5 +1,64 @@
|
|
|
1
1
|
import { useSyncExternalStore } from "react";
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Function that gets called when the state changes.
|
|
5
|
+
*
|
|
6
|
+
* @template T - The state type
|
|
7
|
+
* @param state - The new state
|
|
8
|
+
*/
|
|
9
|
+
export type ViewModelListener = () => void;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Interface for a ViewModel that manages state and notifies subscribers of changes.
|
|
13
|
+
*
|
|
14
|
+
* ViewModels provide a way to manage component state outside of the component tree,
|
|
15
|
+
* allowing multiple components to share and react to the same state. They follow
|
|
16
|
+
* the observer pattern, where components can subscribe to state changes and receive
|
|
17
|
+
* notifications when updates occur.
|
|
18
|
+
*
|
|
19
|
+
* @template T - The type of state managed by this ViewModel
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Implementing a ViewModel
|
|
24
|
+
* class CounterViewModel extends ViewModel<{ count: number }> {
|
|
25
|
+
* increment = () => super.update({ count: super.state.count + 1 });
|
|
26
|
+
* decrement = () => super.update(({ count: super.state.count - 1 }));
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* // Using in a component
|
|
30
|
+
* const counterModel = new CounterViewModel({ count: 0 });
|
|
31
|
+
* const { count } = useModelState(counterModel);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export interface ViewModel<T> {
|
|
35
|
+
/**
|
|
36
|
+
* Subscribe to state changes.
|
|
37
|
+
*
|
|
38
|
+
* The listener will be called immediately after any state update.
|
|
39
|
+
*
|
|
40
|
+
* @param listener - Function to call when state changes
|
|
41
|
+
* @returns Function to unsubscribe the listener
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const unsubscribe = viewModel.subscribe((state) => {
|
|
46
|
+
* console.log('State changed:', state);
|
|
47
|
+
* });
|
|
48
|
+
*
|
|
49
|
+
* // Later, when you want to stop listening:
|
|
50
|
+
* unsubscribe();
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
subscribe(listener: ViewModelListener): () => void;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the current state.
|
|
57
|
+
*
|
|
58
|
+
* @returns The current state
|
|
59
|
+
*/
|
|
60
|
+
get state(): T;
|
|
61
|
+
}
|
|
3
62
|
|
|
4
63
|
/**
|
|
5
64
|
* A React hook that subscribes a component to a ViewModel's state updates.
|
|
@@ -14,7 +73,7 @@ import type { ViewModel, State } from "@view-models/core";
|
|
|
14
73
|
* @example
|
|
15
74
|
* ```tsx
|
|
16
75
|
* class CounterViewModel extends ViewModel<{ count: number }> {
|
|
17
|
-
* increment = () =>
|
|
76
|
+
* increment = () => super.update({ count: super.state.count + 1 });
|
|
18
77
|
* }
|
|
19
78
|
*
|
|
20
79
|
* function Counter() {
|
|
@@ -30,6 +89,6 @@ import type { ViewModel, State } from "@view-models/core";
|
|
|
30
89
|
* }
|
|
31
90
|
* ```
|
|
32
91
|
*/
|
|
33
|
-
export function useModelState<T
|
|
92
|
+
export function useModelState<T>(model: ViewModel<T>): T {
|
|
34
93
|
return useSyncExternalStore(model.subscribe.bind(model), () => model.state);
|
|
35
94
|
}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
package/dist/useModelState.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { ViewModel, State } from "@view-models/core";
|
|
2
|
-
/**
|
|
3
|
-
* A React hook that subscribes a component to a ViewModel's state updates.
|
|
4
|
-
*
|
|
5
|
-
* This hook connects a React component to a ViewModel instance, automatically
|
|
6
|
-
* subscribing to state changes and triggering re-renders when the state updates.
|
|
7
|
-
*
|
|
8
|
-
* @template T - The state type, which must extend the State interface
|
|
9
|
-
* @param model - The ViewModel instance to subscribe to
|
|
10
|
-
* @returns The current state from the ViewModel
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```tsx
|
|
14
|
-
* class CounterViewModel extends ViewModel<{ count: number }> {
|
|
15
|
-
* increment = () => this.update(({ count }) => ({ count: count + 1 }));
|
|
16
|
-
* }
|
|
17
|
-
*
|
|
18
|
-
* function Counter() {
|
|
19
|
-
* const counterModel = useMemo(() => new CounterViewModel({ count: 0 }), []);
|
|
20
|
-
* const { count } = useModelState(counterModel);
|
|
21
|
-
*
|
|
22
|
-
* return (
|
|
23
|
-
* <div>
|
|
24
|
-
* <p>Count: {count}</p>
|
|
25
|
-
* <button onClick={counterModel.increment}>+</button>
|
|
26
|
-
* </div>
|
|
27
|
-
* );
|
|
28
|
-
* }
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export declare function useModelState<T extends State>(model: ViewModel<T>): T;
|
|
32
|
-
//# sourceMappingURL=useModelState.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useModelState.d.ts","sourceRoot":"","sources":["../src/useModelState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAErE"}
|
package/dist/useModelState.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { useSyncExternalStore } from "react";
|
|
2
|
-
/**
|
|
3
|
-
* A React hook that subscribes a component to a ViewModel's state updates.
|
|
4
|
-
*
|
|
5
|
-
* This hook connects a React component to a ViewModel instance, automatically
|
|
6
|
-
* subscribing to state changes and triggering re-renders when the state updates.
|
|
7
|
-
*
|
|
8
|
-
* @template T - The state type, which must extend the State interface
|
|
9
|
-
* @param model - The ViewModel instance to subscribe to
|
|
10
|
-
* @returns The current state from the ViewModel
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```tsx
|
|
14
|
-
* class CounterViewModel extends ViewModel<{ count: number }> {
|
|
15
|
-
* increment = () => this.update(({ count }) => ({ count: count + 1 }));
|
|
16
|
-
* }
|
|
17
|
-
*
|
|
18
|
-
* function Counter() {
|
|
19
|
-
* const counterModel = useMemo(() => new CounterViewModel({ count: 0 }), []);
|
|
20
|
-
* const { count } = useModelState(counterModel);
|
|
21
|
-
*
|
|
22
|
-
* return (
|
|
23
|
-
* <div>
|
|
24
|
-
* <p>Count: {count}</p>
|
|
25
|
-
* <button onClick={counterModel.increment}>+</button>
|
|
26
|
-
* </div>
|
|
27
|
-
* );
|
|
28
|
-
* }
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export function useModelState(model) {
|
|
32
|
-
return useSyncExternalStore(model.subscribe.bind(model), () => model.state);
|
|
33
|
-
}
|