@view-models/react 3.0.0 → 4.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 CHANGED
@@ -46,80 +46,6 @@ function Counter() {
46
46
  }
47
47
  ```
48
48
 
49
- ### Derived State
50
-
51
- Use `useDerivedState` with the `derived` helper to compute values from your model state. The `derived` function creates a memoized mapper that uses reference equality (`Object.is`) to cache results, which works perfectly with immutable state.
52
-
53
- Derived mappers should be defined outside your components:
54
-
55
- ```tsx
56
- import { ViewModel, derived } from "@view-models/core";
57
- import { useDerivedState } from "@view-models/react";
58
-
59
- type TodoItem = {
60
- id: number;
61
- text: string;
62
- completed: boolean;
63
- };
64
-
65
- type TodoState = Readonly<{
66
- items: TodoItem[];
67
- filter: "all" | "active" | "completed";
68
- }>;
69
-
70
- class TodoViewModel extends ViewModel<TodoState> {
71
- // ... methods to modify state
72
- }
73
-
74
- const todoModel = new TodoViewModel({ items: [], filter: "all" });
75
-
76
- // Create derived mappers using the derived helper
77
- const selectStats = derived(({ items }): TodoState) => ({
78
- total: items.length,
79
- completed: items.filter((item) => item.completed).length,
80
- active: items.filter((item) => !item.completed).length,
81
- }));
82
-
83
- function TodoStats() {
84
- // Use the derived mapper with useDerivedState
85
- const stats = useDerivedState(todoModel, selectStats);
86
-
87
- return (
88
- <div>
89
- <p>Total: {stats.total}</p>
90
- <p>Completed: {stats.completed}</p>
91
- <p>Active: {stats.active}</p>
92
- </div>
93
- );
94
- }
95
- ```
96
-
97
- You can create multiple derived mappers for different parts of your state:
98
-
99
- ```tsx
100
- // Create derived mappers outside your components
101
- const selectFilteredItems = derived(({ items, filter }: TodoState) =>
102
- items.filter((item) => {
103
- if (filter === "active") return !item.completed;
104
- if (filter === "completed") return item.completed;
105
- return true;
106
- }),
107
- );
108
-
109
- function TodoList() {
110
- // The mapper only re-runs when the state reference changes
111
- const filteredItems = useDerivedState(todoModel, selectFilteredItems);
112
-
113
- return (
114
- <ul>
115
- {filteredItems.map((item) => (
116
- <li key={item.id}>{item.text}</li>
117
- ))}
118
- </ul>
119
- );
120
- }
121
- ```
122
-
123
49
  ### Creating view models inside components
124
50
 
125
51
  When you need to create a view model from within a React component, use `useMemo` to ensure the model is only created once.
package/dist/index.d.ts CHANGED
@@ -87,53 +87,4 @@ interface ViewModel<T> {
87
87
  */
88
88
  declare function useModelState<T>(model: ViewModel<T>): T;
89
89
 
90
- type Mapper<I, O> = (input: I) => O;
91
- /**
92
- * A branded type for derived state mapper functions created by the `derived` utility.
93
- * This type ensures that mapper functions are properly memoized before being used
94
- * with hooks like `useDerivedState`.
95
- */
96
- type DerivedMapper<I, O> = Mapper<I, O> & {
97
- __brand: "derived";
98
- };
99
- /**
100
- * A React hook that subscribes to a ViewModel and returns derived state.
101
- *
102
- * This hook combines `useModelState` with a derived mapper function to compute derived values
103
- * from the model's state. The component will re-render whenever the model state changes.
104
- *
105
- * The mapper must be created using the `derived` utility, which provides memoization
106
- * based on state identity to prevent unnecessary recalculations.
107
- *
108
- * @template S - The model state type
109
- * @template D - The derived state type
110
- * @param model - The ViewModel instance to subscribe to
111
- * @param mapper - A derived mapper function created with the `derived` utility
112
- * @returns The derived state computed from the current model state
113
- *
114
- * @example
115
- * ```tsx
116
- * import { ViewModel, derived } from "@view-models/core";
117
- * import { useDerivedState } from "@view-models/react";
118
- *
119
- * type TodoState = {
120
- * items: Array<{ id: string; text: string; completed: boolean }>;
121
- * };
122
- *
123
- * const todoModel = new ViewModel<TodoState>({ items: [] });
124
- *
125
- * // Create a derived mapper function
126
- * const selectCompletedCount = derived(({ items }: TodoState) => ({
127
- * completed: items.filter(item => item.completed).length,
128
- * total: items.length
129
- * }));
130
- *
131
- * function TodoStats() {
132
- * const stats = useDerivedState(todoModel, selectCompletedCount);
133
- * return <div>{stats.completed} of {stats.total} completed</div>;
134
- * }
135
- * ```
136
- */
137
- declare const useDerivedState: <S, D>(model: ViewModel<S>, mapper: DerivedMapper<S, D>) => D;
138
-
139
- export { useDerivedState, useModelState };
90
+ export { useModelState };
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
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};
1
+ import{useSyncExternalStore as r}from"react";function t(t){return r(t.subscribe.bind(t),()=>t.state)}export{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"],"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/useModelState.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"],"names":["useModelState","model","useSyncExternalStore","subscribe","bind","state"],"mappings":"6CAgCM,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": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "React integration for @view-models/core",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,6 +55,7 @@
55
55
  "@testing-library/react": "^16.1.0",
56
56
  "@types/react": "^18.3.18",
57
57
  "@view-models/core": "^4.1.0",
58
+ "@view-models/react": "^3.0.0",
58
59
  "@vitest/coverage-v8": "^2.1.8",
59
60
  "jsdom": "^26.0.0",
60
61
  "prettier": "^3.4.2",
package/src/index.ts CHANGED
@@ -1,2 +1 @@
1
1
  export * from "./useModelState.js";
2
- export * from "./useDerivedState.js";
@@ -1,54 +0,0 @@
1
- import { useModelState } from "./useModelState";
2
- import { ViewModel } from "./ViewModel";
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" };
12
-
13
- /**
14
- * A React hook that subscribes to a ViewModel and returns derived state.
15
- *
16
- * This hook combines `useModelState` with a derived mapper function to compute derived values
17
- * from the model's state. The component will re-render whenever the model state changes.
18
- *
19
- * The mapper must be created using the `derived` utility, which provides memoization
20
- * based on state identity to prevent unnecessary recalculations.
21
- *
22
- * @template S - The model state type
23
- * @template D - The derived state type
24
- * @param model - The ViewModel instance to subscribe to
25
- * @param mapper - A derived mapper function created with the `derived` utility
26
- * @returns The derived state computed from the current model state
27
- *
28
- * @example
29
- * ```tsx
30
- * import { ViewModel, derived } from "@view-models/core";
31
- * import { useDerivedState } from "@view-models/react";
32
- *
33
- * type TodoState = {
34
- * items: Array<{ id: string; text: string; completed: boolean }>;
35
- * };
36
- *
37
- * const todoModel = new ViewModel<TodoState>({ items: [] });
38
- *
39
- * // Create a derived mapper function
40
- * const selectCompletedCount = derived(({ items }: TodoState) => ({
41
- * completed: items.filter(item => item.completed).length,
42
- * total: items.length
43
- * }));
44
- *
45
- * function TodoStats() {
46
- * const stats = useDerivedState(todoModel, selectCompletedCount);
47
- * return <div>{stats.completed} of {stats.total} completed</div>;
48
- * }
49
- * ```
50
- */
51
- export const useDerivedState = <S, D>(
52
- model: ViewModel<S>,
53
- mapper: DerivedMapper<S, D>,
54
- ) => mapper(useModelState(model));