@view-models/react 2.0.0 → 3.0.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 +2 -2
- package/dist/index.d.ts +3 -31
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +0 -1
- package/src/useDerivedState.ts +11 -3
- package/src/derived.ts +0 -46
package/README.md
CHANGED
|
@@ -53,8 +53,8 @@ Use `useDerivedState` with the `derived` helper to compute values from your mode
|
|
|
53
53
|
Derived mappers should be defined outside your components:
|
|
54
54
|
|
|
55
55
|
```tsx
|
|
56
|
-
import { ViewModel } from "@view-models/core";
|
|
57
|
-
import { useDerivedState
|
|
56
|
+
import { ViewModel, derived } from "@view-models/core";
|
|
57
|
+
import { useDerivedState } from "@view-models/react";
|
|
58
58
|
|
|
59
59
|
type TodoItem = {
|
|
60
60
|
id: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -96,33 +96,6 @@ type Mapper<I, O> = (input: I) => O;
|
|
|
96
96
|
type DerivedMapper<I, O> = Mapper<I, O> & {
|
|
97
97
|
__brand: "derived";
|
|
98
98
|
};
|
|
99
|
-
/**
|
|
100
|
-
* Creates a memoized derived state mapper function that caches results based on input identity.
|
|
101
|
-
*
|
|
102
|
-
* The memoization uses `Object.is` to compare inputs, making it ideal for use with
|
|
103
|
-
* immutable state objects. When the input reference hasn't changed, the cached output
|
|
104
|
-
* is returned without re-executing the function.
|
|
105
|
-
*
|
|
106
|
-
* This is the required way to create mapper functions for use with `useDerivedState`.
|
|
107
|
-
*
|
|
108
|
-
* @template I - The input type
|
|
109
|
-
* @template O - The output type
|
|
110
|
-
* @param fn - A pure function that transforms input to output
|
|
111
|
-
* @returns A memoized derived mapper function
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* ```ts
|
|
115
|
-
* const selectStats = derived(({ items }: AppState) => ({
|
|
116
|
-
* total: items.reduce((sum, item) => sum + item.price, 0),
|
|
117
|
-
* count: items.length
|
|
118
|
-
* }));
|
|
119
|
-
*
|
|
120
|
-
* // Use with useDerivedState
|
|
121
|
-
* const stats = useDerivedState(model, selectStats);
|
|
122
|
-
* ```
|
|
123
|
-
*/
|
|
124
|
-
declare const derived: <I, O>(fn: Mapper<I, O>) => DerivedMapper<I, O>;
|
|
125
|
-
|
|
126
99
|
/**
|
|
127
100
|
* A React hook that subscribes to a ViewModel and returns derived state.
|
|
128
101
|
*
|
|
@@ -140,8 +113,8 @@ declare const derived: <I, O>(fn: Mapper<I, O>) => DerivedMapper<I, O>;
|
|
|
140
113
|
*
|
|
141
114
|
* @example
|
|
142
115
|
* ```tsx
|
|
143
|
-
* import { ViewModel } from "@view-models/core";
|
|
144
|
-
* import { useDerivedState
|
|
116
|
+
* import { ViewModel, derived } from "@view-models/core";
|
|
117
|
+
* import { useDerivedState } from "@view-models/react";
|
|
145
118
|
*
|
|
146
119
|
* type TodoState = {
|
|
147
120
|
* items: Array<{ id: string; text: string; completed: boolean }>;
|
|
@@ -163,5 +136,4 @@ declare const derived: <I, O>(fn: Mapper<I, O>) => DerivedMapper<I, O>;
|
|
|
163
136
|
*/
|
|
164
137
|
declare const useDerivedState: <S, D>(model: ViewModel<S>, mapper: DerivedMapper<S, D>) => D;
|
|
165
138
|
|
|
166
|
-
export {
|
|
167
|
-
export type { DerivedMapper };
|
|
139
|
+
export { useDerivedState, useModelState };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{useSyncExternalStore as
|
|
1
|
+
import{useSyncExternalStore as r}from"react";function t(t){return r(t.subscribe.bind(t),()=>t.state)}const o=(r,o)=>o(t(r));export{o as useDerivedState,t as useModelState};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/useModelState.ts","../src/useDerivedState.ts"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/useModelState.ts","../src/useDerivedState.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\nimport { ViewModel } from \"./ViewModel\";\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","import { useModelState } from \"./useModelState\";\nimport { ViewModel } from \"./ViewModel\";\n\ntype Mapper<I, O> = (input: I) => O;\n\n/**\n * A branded type for derived state mapper functions created by the `derived` utility.\n * This type ensures that mapper functions are properly memoized before being used\n * with hooks like `useDerivedState`.\n */\ntype DerivedMapper<I, O> = Mapper<I, O> & { __brand: \"derived\" };\n\n/**\n * A React hook that subscribes to a ViewModel and returns derived state.\n *\n * This hook combines `useModelState` with a derived mapper function to compute derived values\n * from the model's state. The component will re-render whenever the model state changes.\n *\n * The mapper must be created using the `derived` utility, which provides memoization\n * based on state identity to prevent unnecessary recalculations.\n *\n * @template S - The model state type\n * @template D - The derived state type\n * @param model - The ViewModel instance to subscribe to\n * @param mapper - A derived mapper function created with the `derived` utility\n * @returns The derived state computed from the current model state\n *\n * @example\n * ```tsx\n * import { ViewModel, derived } from \"@view-models/core\";\n * import { useDerivedState } from \"@view-models/react\";\n *\n * type TodoState = {\n * items: Array<{ id: string; text: string; completed: boolean }>;\n * };\n *\n * const todoModel = new ViewModel<TodoState>({ items: [] });\n *\n * // Create a derived mapper function\n * const selectCompletedCount = derived(({ items }: TodoState) => ({\n * completed: items.filter(item => item.completed).length,\n * total: items.length\n * }));\n *\n * function TodoStats() {\n * const stats = useDerivedState(todoModel, selectCompletedCount);\n * return <div>{stats.completed} of {stats.total} completed</div>;\n * }\n * ```\n */\nexport const useDerivedState = <S, D>(\n model: ViewModel<S>,\n mapper: DerivedMapper<S, D>,\n) => mapper(useModelState(model));\n"],"names":["useModelState","model","useSyncExternalStore","subscribe","bind","state","useDerivedState","mapper"],"mappings":"6CAgCM,SAAUA,EAAiBC,GAC/B,OAAOC,EAAqBD,EAAME,UAAUC,KAAKH,GAAQ,IAAMA,EAAMI,MACvE,CCgBO,MAAMC,EAAkB,CAC7BL,EACAM,IACGA,EAAOP,EAAcC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@view-models/react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "React integration for @view-models/core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
55
55
|
"@testing-library/react": "^16.1.0",
|
|
56
56
|
"@types/react": "^18.3.18",
|
|
57
|
-
"@view-models/core": "^4.
|
|
57
|
+
"@view-models/core": "^4.1.0",
|
|
58
58
|
"@vitest/coverage-v8": "^2.1.8",
|
|
59
59
|
"jsdom": "^26.0.0",
|
|
60
60
|
"prettier": "^3.4.2",
|
package/src/index.ts
CHANGED
package/src/useDerivedState.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { useModelState } from "./useModelState";
|
|
2
2
|
import { ViewModel } from "./ViewModel";
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
type Mapper<I, O> = (input: I) => O;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A branded type for derived state mapper functions created by the `derived` utility.
|
|
8
|
+
* This type ensures that mapper functions are properly memoized before being used
|
|
9
|
+
* with hooks like `useDerivedState`.
|
|
10
|
+
*/
|
|
11
|
+
type DerivedMapper<I, O> = Mapper<I, O> & { __brand: "derived" };
|
|
4
12
|
|
|
5
13
|
/**
|
|
6
14
|
* A React hook that subscribes to a ViewModel and returns derived state.
|
|
@@ -19,8 +27,8 @@ import { DerivedMapper } from "./derived";
|
|
|
19
27
|
*
|
|
20
28
|
* @example
|
|
21
29
|
* ```tsx
|
|
22
|
-
* import { ViewModel } from "@view-models/core";
|
|
23
|
-
* import { useDerivedState
|
|
30
|
+
* import { ViewModel, derived } from "@view-models/core";
|
|
31
|
+
* import { useDerivedState } from "@view-models/react";
|
|
24
32
|
*
|
|
25
33
|
* type TodoState = {
|
|
26
34
|
* items: Array<{ id: string; text: string; completed: boolean }>;
|
package/src/derived.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
type Mapper<I, O> = (input: I) => O;
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A branded type for derived state mapper functions created by the `derived` utility.
|
|
5
|
-
* This type ensures that mapper functions are properly memoized before being used
|
|
6
|
-
* with hooks like `useDerivedState`.
|
|
7
|
-
*/
|
|
8
|
-
export type DerivedMapper<I, O> = Mapper<I, O> & { __brand: "derived" };
|
|
9
|
-
|
|
10
|
-
const UNINITIALIZED = {};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Creates a memoized derived state mapper function that caches results based on input identity.
|
|
14
|
-
*
|
|
15
|
-
* The memoization uses `Object.is` to compare inputs, making it ideal for use with
|
|
16
|
-
* immutable state objects. When the input reference hasn't changed, the cached output
|
|
17
|
-
* is returned without re-executing the function.
|
|
18
|
-
*
|
|
19
|
-
* This is the required way to create mapper functions for use with `useDerivedState`.
|
|
20
|
-
*
|
|
21
|
-
* @template I - The input type
|
|
22
|
-
* @template O - The output type
|
|
23
|
-
* @param fn - A pure function that transforms input to output
|
|
24
|
-
* @returns A memoized derived mapper function
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* ```ts
|
|
28
|
-
* const selectStats = derived(({ items }: AppState) => ({
|
|
29
|
-
* total: items.reduce((sum, item) => sum + item.price, 0),
|
|
30
|
-
* count: items.length
|
|
31
|
-
* }));
|
|
32
|
-
*
|
|
33
|
-
* // Use with useDerivedState
|
|
34
|
-
* const stats = useDerivedState(model, selectStats);
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
export const derived = <I, O>(fn: Mapper<I, O>): DerivedMapper<I, O> => {
|
|
38
|
-
let lastInput: I | typeof UNINITIALIZED = UNINITIALIZED;
|
|
39
|
-
let lastOutput: O | typeof UNINITIALIZED = UNINITIALIZED;
|
|
40
|
-
return ((input: I) => {
|
|
41
|
-
if (lastInput !== UNINITIALIZED && Object.is(lastInput, input)) {
|
|
42
|
-
return lastOutput as O;
|
|
43
|
-
}
|
|
44
|
-
return (lastOutput = fn((lastInput = input)));
|
|
45
|
-
}) as DerivedMapper<I, O>;
|
|
46
|
-
};
|