@montra-interactive/deepstate-react 0.1.4 → 0.2.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/dist/hooks.d.ts CHANGED
@@ -35,81 +35,46 @@ type ObservableObjectValues<T extends Record<string, Observable<unknown>>> = {
35
35
  */
36
36
  export declare function useObservable<T>(observable$: Observable<T>, getSnapshot: () => T): T;
37
37
  /**
38
- * Hook to get the current value of a deepstate node.
39
- * Re-renders the component whenever the node's value changes.
38
+ * Hook to get values from one or more deepstate nodes, optionally with a selector function.
39
+ * Re-renders the component whenever the selected value changes.
40
40
  *
41
41
  * This is the primary hook for using deepstate in React.
42
- * Works with any deepstate node: RxLeaf, RxObject, RxArray, or RxNullable.
43
- *
44
42
  * Uses React 18's useSyncExternalStore for concurrent-mode safety.
45
43
  *
46
- * @param node - A deepstate node (any reactive property from your state)
47
- * @returns The current value of the node (deeply readonly)
48
- *
49
- * @example
44
+ * @example Single node (get raw value)
50
45
  * ```tsx
51
46
  * import { state } from 'deepstate';
52
- * import { useStateValue } from 'deepstate-react';
47
+ * import { useSelect } from 'deepstate-react';
53
48
  *
54
49
  * const store = state({
55
50
  * user: { name: 'Alice', age: 30 },
56
- * items: [{ id: 1, name: 'Item 1' }],
57
51
  * count: 0
58
52
  * });
59
53
  *
60
54
  * // Subscribe to a primitive
61
55
  * function Counter() {
62
- * const count = useStateValue(store.count);
56
+ * const count = useSelect(store.count);
63
57
  * return <span>{count}</span>;
64
58
  * }
65
59
  *
66
60
  * // Subscribe to an object
67
61
  * function UserCard() {
68
- * const user = useStateValue(store.user);
62
+ * const user = useSelect(store.user);
69
63
  * return <div>{user.name}, {user.age}</div>;
70
64
  * }
71
65
  *
72
66
  * // Subscribe to a nested property (fine-grained!)
73
67
  * function UserName() {
74
- * const name = useStateValue(store.user.name);
68
+ * const name = useSelect(store.user.name);
75
69
  * return <span>{name}</span>;
76
70
  * }
77
- *
78
- * // Subscribe to an array
79
- * function ItemList() {
80
- * const items = useStateValue(store.items);
81
- * return <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
82
- * }
83
71
  * ```
84
- */
85
- export declare function useStateValue<T extends Observable<unknown>>(node: T): ObservableValue<T>;
86
- /**
87
- * Hook to derive a value from one or more deepstate nodes with a selector function.
88
- * Only re-renders when the derived value changes (using reference equality by default).
89
- *
90
- * Use this when you need to compute/transform a value from state.
91
- * The selector runs on every emission but only triggers re-render if result changes.
92
- *
93
- * Uses React 18's useSyncExternalStore for concurrent-mode safety.
94
72
  *
95
- * @param node - A deepstate node, array of nodes, or object of nodes to select from
96
- * @param selector - Function to derive a value from the node's value(s)
97
- * @param equalityFn - Optional custom equality function (default: Object.is)
98
- * @returns The derived value
99
- *
100
- * @example Single node
73
+ * @example Single node with selector (derive a value)
101
74
  * ```tsx
102
- * import { state } from 'deepstate';
103
- * import { useSelector } from 'deepstate-react';
104
- *
105
- * const store = state({
106
- * user: { firstName: 'Alice', lastName: 'Smith', age: 30 },
107
- * items: [{ id: 1, price: 10 }, { id: 2, price: 20 }]
108
- * });
109
- *
110
75
  * // Derive a computed value from a single node
111
76
  * function FullName() {
112
- * const fullName = useSelector(
77
+ * const fullName = useSelect(
113
78
  * store.user,
114
79
  * user => `${user.firstName} ${user.lastName}`
115
80
  * );
@@ -121,7 +86,7 @@ export declare function useStateValue<T extends Observable<unknown>>(node: T): O
121
86
  * ```tsx
122
87
  * // Combine multiple nodes - receives values as tuple
123
88
  * function Progress() {
124
- * const percentage = useSelector(
89
+ * const percentage = useSelect(
125
90
  * [store.completed, store.total],
126
91
  * ([completed, total]) => total > 0 ? (completed / total) * 100 : 0
127
92
  * );
@@ -133,7 +98,7 @@ export declare function useStateValue<T extends Observable<unknown>>(node: T): O
133
98
  * ```tsx
134
99
  * // Combine multiple nodes - receives values as object
135
100
  * function Progress() {
136
- * const percentage = useSelector(
101
+ * const percentage = useSelect(
137
102
  * { completed: store.completed, total: store.total },
138
103
  * ({ completed, total }) => total > 0 ? (completed / total) * 100 : 0
139
104
  * );
@@ -144,7 +109,7 @@ export declare function useStateValue<T extends Observable<unknown>>(node: T): O
144
109
  * @example With custom equality
145
110
  * ```tsx
146
111
  * function ItemIds() {
147
- * const ids = useSelector(
112
+ * const ids = useSelect(
148
113
  * store.items,
149
114
  * items => items.map(i => i.id),
150
115
  * (a, b) => a.length === b.length && a.every((v, i) => v === b[i])
@@ -153,11 +118,20 @@ export declare function useStateValue<T extends Observable<unknown>>(node: T): O
153
118
  * }
154
119
  * ```
155
120
  */
156
- export declare function useSelector<T extends Observable<unknown>, R>(node: T, selector: (value: ObservableValue<T>) => R, equalityFn?: (a: R, b: R) => boolean): R;
157
- export declare function useSelector<T1 extends Observable<unknown>, T2 extends Observable<unknown>, R>(nodes: [T1, T2], selector: (values: [ObservableValue<T1>, ObservableValue<T2>]) => R, equalityFn?: (a: R, b: R) => boolean): R;
158
- export declare function useSelector<T1 extends Observable<unknown>, T2 extends Observable<unknown>, T3 extends Observable<unknown>, R>(nodes: [T1, T2, T3], selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>]) => R, equalityFn?: (a: R, b: R) => boolean): R;
159
- export declare function useSelector<T1 extends Observable<unknown>, T2 extends Observable<unknown>, T3 extends Observable<unknown>, T4 extends Observable<unknown>, R>(nodes: [T1, T2, T3, T4], selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>, ObservableValue<T4>]) => R, equalityFn?: (a: R, b: R) => boolean): R;
160
- export declare function useSelector<T1 extends Observable<unknown>, T2 extends Observable<unknown>, T3 extends Observable<unknown>, T4 extends Observable<unknown>, T5 extends Observable<unknown>, R>(nodes: [T1, T2, T3, T4, T5], selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>, ObservableValue<T4>, ObservableValue<T5>]) => R, equalityFn?: (a: R, b: R) => boolean): R;
161
- export declare function useSelector<T extends Record<string, Observable<unknown>>, R>(nodes: T, selector: (values: ObservableObjectValues<T>) => R, equalityFn?: (a: R, b: R) => boolean): R;
121
+ export declare function useSelect<T extends Observable<unknown>>(node: T): ObservableValue<T>;
122
+ export declare function useSelect<T extends Observable<unknown>, R>(node: T, selector: (value: ObservableValue<T>) => R, equalityFn?: (a: R, b: R) => boolean): R;
123
+ export declare function useSelect<T1 extends Observable<unknown>, T2 extends Observable<unknown>, R>(nodes: [T1, T2], selector: (values: [ObservableValue<T1>, ObservableValue<T2>]) => R, equalityFn?: (a: R, b: R) => boolean): R;
124
+ export declare function useSelect<T1 extends Observable<unknown>, T2 extends Observable<unknown>, T3 extends Observable<unknown>, R>(nodes: [T1, T2, T3], selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>]) => R, equalityFn?: (a: R, b: R) => boolean): R;
125
+ export declare function useSelect<T1 extends Observable<unknown>, T2 extends Observable<unknown>, T3 extends Observable<unknown>, T4 extends Observable<unknown>, R>(nodes: [T1, T2, T3, T4], selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>, ObservableValue<T4>]) => R, equalityFn?: (a: R, b: R) => boolean): R;
126
+ export declare function useSelect<T1 extends Observable<unknown>, T2 extends Observable<unknown>, T3 extends Observable<unknown>, T4 extends Observable<unknown>, T5 extends Observable<unknown>, R>(nodes: [T1, T2, T3, T4, T5], selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>, ObservableValue<T4>, ObservableValue<T5>]) => R, equalityFn?: (a: R, b: R) => boolean): R;
127
+ export declare function useSelect<T extends Record<string, Observable<unknown>>, R>(nodes: T, selector: (values: ObservableObjectValues<T>) => R, equalityFn?: (a: R, b: R) => boolean): R;
128
+ /**
129
+ * @deprecated Use `useSelect` instead. This is an alias for backwards compatibility.
130
+ */
131
+ export declare const useStateValue: typeof useSelect;
132
+ /**
133
+ * @deprecated Use `useSelect` instead. This is an alias for backwards compatibility.
134
+ */
135
+ export declare const useSelector: typeof useSelect;
162
136
  export {};
163
137
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -6,21 +6,32 @@
6
6
  * @example
7
7
  * ```tsx
8
8
  * import { state } from 'deepstate';
9
- * import { useStateValue, useSelector } from 'deepstate-react';
9
+ * import { useSelect } from 'deepstate-react';
10
10
  *
11
11
  * const store = state({ user: { name: 'Alice', age: 30 }, count: 0 });
12
12
  *
13
+ * // Get raw value
13
14
  * function UserName() {
14
- * const name = useStateValue(store.user.name);
15
+ * const name = useSelect(store.user.name);
15
16
  * return <span>{name}</span>;
16
17
  * }
17
18
  *
19
+ * // With selector
18
20
  * function UserSummary() {
19
- * const summary = useSelector(store.user, user => `${user.name} (${user.age})`);
21
+ * const summary = useSelect(store.user, user => `${user.name} (${user.age})`);
20
22
  * return <span>{summary}</span>;
21
23
  * }
24
+ *
25
+ * // Combine multiple nodes
26
+ * function Progress() {
27
+ * const pct = useSelect(
28
+ * [store.completed, store.total],
29
+ * ([completed, total]) => total > 0 ? (completed / total) * 100 : 0
30
+ * );
31
+ * return <span>{pct}%</span>;
32
+ * }
22
33
  * ```
23
34
  */
24
- export { useStateValue, useSelector, useObservable, } from "./hooks";
35
+ export { useSelect, useObservable, useStateValue, useSelector, } from "./hooks";
25
36
  export type { Observable } from "rxjs";
26
37
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EACL,aAAa,EACb,WAAW,EACX,aAAa,GACd,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,SAAS,EACT,aAAa,EAEb,aAAa,EACb,WAAW,GACZ,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -20,27 +20,16 @@ function useObservable(observable$, getSnapshot) {
20
20
  const getSnapshotMemo = useCallback(() => valueRef.current, []);
21
21
  return useSyncExternalStore(subscribe, getSnapshotMemo, getSnapshotMemo);
22
22
  }
23
- function useStateValue(node) {
24
- const valueRef = useRef(hasGet(node) ? node.get() : undefined);
25
- const subscribe = useCallback((onStoreChange) => {
26
- const subscription = node.subscribe((newValue) => {
27
- valueRef.current = newValue;
28
- onStoreChange();
29
- });
30
- return () => subscription.unsubscribe();
31
- }, [node]);
32
- const getSnapshot = useCallback(() => valueRef.current, []);
33
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
34
- }
35
- function useSelector(nodeOrNodes, selector, equalityFn = Object.is) {
23
+ function useSelect(nodeOrNodes, selector, equalityFn = Object.is) {
36
24
  const { combined$, getInitialValue } = useMemo(() => {
37
25
  if (Array.isArray(nodeOrNodes)) {
38
26
  const nodes = nodeOrNodes;
27
+ const sel = selector;
39
28
  return {
40
- combined$: combineLatest(nodes).pipe(map((values) => selector(values)), distinctUntilChanged(equalityFn)),
29
+ combined$: combineLatest(nodes).pipe(map((values) => sel(values)), distinctUntilChanged(equalityFn)),
41
30
  getInitialValue: () => {
42
31
  const values = nodes.map((n) => hasGet(n) ? n.get() : undefined);
43
- return selector(values);
32
+ return sel(values);
44
33
  }
45
34
  };
46
35
  }
@@ -48,13 +37,14 @@ function useSelector(nodeOrNodes, selector, equalityFn = Object.is) {
48
37
  const obj = nodeOrNodes;
49
38
  const keys = Object.keys(obj);
50
39
  const observables = keys.map((k) => obj[k]);
40
+ const sel = selector;
51
41
  return {
52
42
  combined$: combineLatest(observables).pipe(map((values) => {
53
43
  const result = {};
54
44
  keys.forEach((key, i) => {
55
45
  result[key] = values[i];
56
46
  });
57
- return selector(result);
47
+ return sel(result);
58
48
  }), distinctUntilChanged(equalityFn)),
59
49
  getInitialValue: () => {
60
50
  const result = {};
@@ -62,20 +52,32 @@ function useSelector(nodeOrNodes, selector, equalityFn = Object.is) {
62
52
  const node2 = obj[key];
63
53
  result[key] = hasGet(node2) ? node2.get() : undefined;
64
54
  });
65
- return selector(result);
55
+ return sel(result);
66
56
  }
67
57
  };
68
58
  }
69
59
  const node = nodeOrNodes;
70
- return {
71
- combined$: node.pipe(map((value) => selector(value)), distinctUntilChanged(equalityFn)),
72
- getInitialValue: () => {
73
- if (hasGet(node)) {
74
- return selector(node.get());
60
+ if (selector) {
61
+ return {
62
+ combined$: node.pipe(map((value) => selector(value)), distinctUntilChanged(equalityFn)),
63
+ getInitialValue: () => {
64
+ if (hasGet(node)) {
65
+ return selector(node.get());
66
+ }
67
+ return;
68
+ }
69
+ };
70
+ } else {
71
+ return {
72
+ combined$: node.pipe(distinctUntilChanged(equalityFn)),
73
+ getInitialValue: () => {
74
+ if (hasGet(node)) {
75
+ return node.get();
76
+ }
77
+ return;
75
78
  }
76
- return;
77
- }
78
- };
79
+ };
80
+ }
79
81
  }, [nodeOrNodes, selector, equalityFn]);
80
82
  const valueRef = useRef(getInitialValue());
81
83
  const subscribe = useCallback((onStoreChange) => {
@@ -88,8 +90,11 @@ function useSelector(nodeOrNodes, selector, equalityFn = Object.is) {
88
90
  const getSnapshot = useCallback(() => valueRef.current, []);
89
91
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
90
92
  }
93
+ var useStateValue = useSelect;
94
+ var useSelector = useSelect;
91
95
  export {
92
96
  useStateValue,
93
97
  useSelector,
98
+ useSelect,
94
99
  useObservable
95
100
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@montra-interactive/deepstate-react",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "React bindings for deepstate - Proxy-based reactive state management with RxJS.",
5
5
  "keywords": [
6
6
  "react",
package/src/hooks.ts CHANGED
@@ -91,107 +91,46 @@ export function useObservable<T>(
91
91
  }
92
92
 
93
93
  /**
94
- * Hook to get the current value of a deepstate node.
95
- * Re-renders the component whenever the node's value changes.
94
+ * Hook to get values from one or more deepstate nodes, optionally with a selector function.
95
+ * Re-renders the component whenever the selected value changes.
96
96
  *
97
97
  * This is the primary hook for using deepstate in React.
98
- * Works with any deepstate node: RxLeaf, RxObject, RxArray, or RxNullable.
99
- *
100
98
  * Uses React 18's useSyncExternalStore for concurrent-mode safety.
101
99
  *
102
- * @param node - A deepstate node (any reactive property from your state)
103
- * @returns The current value of the node (deeply readonly)
104
- *
105
- * @example
100
+ * @example Single node (get raw value)
106
101
  * ```tsx
107
102
  * import { state } from 'deepstate';
108
- * import { useStateValue } from 'deepstate-react';
103
+ * import { useSelect } from 'deepstate-react';
109
104
  *
110
105
  * const store = state({
111
106
  * user: { name: 'Alice', age: 30 },
112
- * items: [{ id: 1, name: 'Item 1' }],
113
107
  * count: 0
114
108
  * });
115
109
  *
116
110
  * // Subscribe to a primitive
117
111
  * function Counter() {
118
- * const count = useStateValue(store.count);
112
+ * const count = useSelect(store.count);
119
113
  * return <span>{count}</span>;
120
114
  * }
121
115
  *
122
116
  * // Subscribe to an object
123
117
  * function UserCard() {
124
- * const user = useStateValue(store.user);
118
+ * const user = useSelect(store.user);
125
119
  * return <div>{user.name}, {user.age}</div>;
126
120
  * }
127
121
  *
128
122
  * // Subscribe to a nested property (fine-grained!)
129
123
  * function UserName() {
130
- * const name = useStateValue(store.user.name);
124
+ * const name = useSelect(store.user.name);
131
125
  * return <span>{name}</span>;
132
126
  * }
133
- *
134
- * // Subscribe to an array
135
- * function ItemList() {
136
- * const items = useStateValue(store.items);
137
- * return <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
138
- * }
139
127
  * ```
140
- */
141
- export function useStateValue<T extends Observable<unknown>>(
142
- node: T
143
- ): ObservableValue<T> {
144
- // Ref to hold the current value - updated by subscription
145
- const valueRef = useRef<ObservableValue<T>>(
146
- hasGet<ObservableValue<T>>(node) ? node.get() : (undefined as ObservableValue<T>)
147
- );
148
-
149
- // Subscribe callback for useSyncExternalStore
150
- const subscribe = useCallback(
151
- (onStoreChange: () => void) => {
152
- const subscription = node.subscribe((newValue) => {
153
- valueRef.current = newValue as ObservableValue<T>;
154
- onStoreChange();
155
- });
156
-
157
- return () => subscription.unsubscribe();
158
- },
159
- [node]
160
- );
161
-
162
- // Get snapshot - just returns the ref value
163
- const getSnapshot = useCallback(() => valueRef.current, []);
164
-
165
- return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
166
- }
167
-
168
- /**
169
- * Hook to derive a value from one or more deepstate nodes with a selector function.
170
- * Only re-renders when the derived value changes (using reference equality by default).
171
- *
172
- * Use this when you need to compute/transform a value from state.
173
- * The selector runs on every emission but only triggers re-render if result changes.
174
128
  *
175
- * Uses React 18's useSyncExternalStore for concurrent-mode safety.
176
- *
177
- * @param node - A deepstate node, array of nodes, or object of nodes to select from
178
- * @param selector - Function to derive a value from the node's value(s)
179
- * @param equalityFn - Optional custom equality function (default: Object.is)
180
- * @returns The derived value
181
- *
182
- * @example Single node
129
+ * @example Single node with selector (derive a value)
183
130
  * ```tsx
184
- * import { state } from 'deepstate';
185
- * import { useSelector } from 'deepstate-react';
186
- *
187
- * const store = state({
188
- * user: { firstName: 'Alice', lastName: 'Smith', age: 30 },
189
- * items: [{ id: 1, price: 10 }, { id: 2, price: 20 }]
190
- * });
191
- *
192
131
  * // Derive a computed value from a single node
193
132
  * function FullName() {
194
- * const fullName = useSelector(
133
+ * const fullName = useSelect(
195
134
  * store.user,
196
135
  * user => `${user.firstName} ${user.lastName}`
197
136
  * );
@@ -203,7 +142,7 @@ export function useStateValue<T extends Observable<unknown>>(
203
142
  * ```tsx
204
143
  * // Combine multiple nodes - receives values as tuple
205
144
  * function Progress() {
206
- * const percentage = useSelector(
145
+ * const percentage = useSelect(
207
146
  * [store.completed, store.total],
208
147
  * ([completed, total]) => total > 0 ? (completed / total) * 100 : 0
209
148
  * );
@@ -215,7 +154,7 @@ export function useStateValue<T extends Observable<unknown>>(
215
154
  * ```tsx
216
155
  * // Combine multiple nodes - receives values as object
217
156
  * function Progress() {
218
- * const percentage = useSelector(
157
+ * const percentage = useSelect(
219
158
  * { completed: store.completed, total: store.total },
220
159
  * ({ completed, total }) => total > 0 ? (completed / total) * 100 : 0
221
160
  * );
@@ -226,7 +165,7 @@ export function useStateValue<T extends Observable<unknown>>(
226
165
  * @example With custom equality
227
166
  * ```tsx
228
167
  * function ItemIds() {
229
- * const ids = useSelector(
168
+ * const ids = useSelect(
230
169
  * store.items,
231
170
  * items => items.map(i => i.id),
232
171
  * (a, b) => a.length === b.length && a.every((v, i) => v === b[i])
@@ -235,14 +174,18 @@ export function useStateValue<T extends Observable<unknown>>(
235
174
  * }
236
175
  * ```
237
176
  */
238
- // Single node overload
239
- export function useSelector<T extends Observable<unknown>, R>(
177
+ // Single node, no selector - return raw value
178
+ export function useSelect<T extends Observable<unknown>>(
179
+ node: T
180
+ ): ObservableValue<T>;
181
+ // Single node with selector
182
+ export function useSelect<T extends Observable<unknown>, R>(
240
183
  node: T,
241
184
  selector: (value: ObservableValue<T>) => R,
242
185
  equalityFn?: (a: R, b: R) => boolean
243
186
  ): R;
244
- // Array of nodes overload
245
- export function useSelector<
187
+ // Array of 2 nodes with selector
188
+ export function useSelect<
246
189
  T1 extends Observable<unknown>,
247
190
  T2 extends Observable<unknown>,
248
191
  R
@@ -251,7 +194,8 @@ export function useSelector<
251
194
  selector: (values: [ObservableValue<T1>, ObservableValue<T2>]) => R,
252
195
  equalityFn?: (a: R, b: R) => boolean
253
196
  ): R;
254
- export function useSelector<
197
+ // Array of 3 nodes with selector
198
+ export function useSelect<
255
199
  T1 extends Observable<unknown>,
256
200
  T2 extends Observable<unknown>,
257
201
  T3 extends Observable<unknown>,
@@ -261,7 +205,8 @@ export function useSelector<
261
205
  selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>]) => R,
262
206
  equalityFn?: (a: R, b: R) => boolean
263
207
  ): R;
264
- export function useSelector<
208
+ // Array of 4 nodes with selector
209
+ export function useSelect<
265
210
  T1 extends Observable<unknown>,
266
211
  T2 extends Observable<unknown>,
267
212
  T3 extends Observable<unknown>,
@@ -272,7 +217,8 @@ export function useSelector<
272
217
  selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>, ObservableValue<T4>]) => R,
273
218
  equalityFn?: (a: R, b: R) => boolean
274
219
  ): R;
275
- export function useSelector<
220
+ // Array of 5 nodes with selector
221
+ export function useSelect<
276
222
  T1 extends Observable<unknown>,
277
223
  T2 extends Observable<unknown>,
278
224
  T3 extends Observable<unknown>,
@@ -284,42 +230,44 @@ export function useSelector<
284
230
  selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>, ObservableValue<T4>, ObservableValue<T5>]) => R,
285
231
  equalityFn?: (a: R, b: R) => boolean
286
232
  ): R;
287
- // Object of nodes overload
288
- export function useSelector<T extends Record<string, Observable<unknown>>, R>(
233
+ // Object of nodes with selector
234
+ export function useSelect<T extends Record<string, Observable<unknown>>, R>(
289
235
  nodes: T,
290
236
  selector: (values: ObservableObjectValues<T>) => R,
291
237
  equalityFn?: (a: R, b: R) => boolean
292
238
  ): R;
293
239
  // Implementation
294
- export function useSelector(
240
+ export function useSelect(
295
241
  nodeOrNodes: Observable<unknown> | Observable<unknown>[] | Record<string, Observable<unknown>>,
296
242
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
297
- selector: (value: any) => any,
243
+ selector?: (value: any) => any,
298
244
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
299
245
  equalityFn: (a: any, b: any) => boolean = Object.is
300
246
  ): unknown {
301
247
  // Determine the form and create the combined observable
302
248
  const { combined$, getInitialValue } = useMemo(() => {
303
- // Array form: [node1, node2, ...]
249
+ // Array form: [node1, node2, ...] - always requires selector
304
250
  if (Array.isArray(nodeOrNodes)) {
305
251
  const nodes = nodeOrNodes as Observable<unknown>[];
252
+ const sel = selector!; // selector is required for array form
306
253
  return {
307
254
  combined$: combineLatest(nodes).pipe(
308
- map((values) => selector(values)),
255
+ map((values) => sel(values)),
309
256
  distinctUntilChanged(equalityFn)
310
257
  ),
311
258
  getInitialValue: (): unknown => {
312
259
  const values = nodes.map((n) => (hasGet<unknown>(n) ? n.get() : undefined));
313
- return selector(values);
260
+ return sel(values);
314
261
  },
315
262
  };
316
263
  }
317
264
 
318
- // Object form: { a: node1, b: node2, ... }
265
+ // Object form: { a: node1, b: node2, ... } - always requires selector
319
266
  if (!isObservable(nodeOrNodes)) {
320
267
  const obj = nodeOrNodes as Record<string, Observable<unknown>>;
321
268
  const keys = Object.keys(obj);
322
269
  const observables = keys.map((k) => obj[k]);
270
+ const sel = selector!; // selector is required for object form
323
271
 
324
272
  return {
325
273
  combined$: combineLatest(observables).pipe(
@@ -328,7 +276,7 @@ export function useSelector(
328
276
  keys.forEach((key, i) => {
329
277
  result[key] = values[i];
330
278
  });
331
- return selector(result);
279
+ return sel(result);
332
280
  }),
333
281
  distinctUntilChanged(equalityFn)
334
282
  ),
@@ -338,25 +286,42 @@ export function useSelector(
338
286
  const node = obj[key];
339
287
  result[key] = hasGet<unknown>(node) ? node.get() : undefined;
340
288
  });
341
- return selector(result);
289
+ return sel(result);
342
290
  },
343
291
  };
344
292
  }
345
293
 
346
- // Single node form
294
+ // Single node form - selector is optional
347
295
  const node = nodeOrNodes as Observable<unknown>;
348
- return {
349
- combined$: node.pipe(
350
- map((value) => selector(value)),
351
- distinctUntilChanged(equalityFn)
352
- ),
353
- getInitialValue: (): unknown => {
354
- if (hasGet<unknown>(node)) {
355
- return selector(node.get());
356
- }
357
- return undefined;
358
- },
359
- };
296
+
297
+ if (selector) {
298
+ // With selector - apply transformation
299
+ return {
300
+ combined$: node.pipe(
301
+ map((value) => selector(value)),
302
+ distinctUntilChanged(equalityFn)
303
+ ),
304
+ getInitialValue: (): unknown => {
305
+ if (hasGet<unknown>(node)) {
306
+ return selector(node.get());
307
+ }
308
+ return undefined;
309
+ },
310
+ };
311
+ } else {
312
+ // No selector - return raw value
313
+ return {
314
+ combined$: node.pipe(
315
+ distinctUntilChanged(equalityFn)
316
+ ),
317
+ getInitialValue: (): unknown => {
318
+ if (hasGet<unknown>(node)) {
319
+ return node.get();
320
+ }
321
+ return undefined;
322
+ },
323
+ };
324
+ }
360
325
  }, [nodeOrNodes, selector, equalityFn]);
361
326
 
362
327
  // Ref to hold the current derived value
@@ -380,3 +345,13 @@ export function useSelector(
380
345
 
381
346
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
382
347
  }
348
+
349
+ /**
350
+ * @deprecated Use `useSelect` instead. This is an alias for backwards compatibility.
351
+ */
352
+ export const useStateValue = useSelect;
353
+
354
+ /**
355
+ * @deprecated Use `useSelect` instead. This is an alias for backwards compatibility.
356
+ */
357
+ export const useSelector = useSelect;
package/src/index.ts CHANGED
@@ -6,26 +6,39 @@
6
6
  * @example
7
7
  * ```tsx
8
8
  * import { state } from 'deepstate';
9
- * import { useStateValue, useSelector } from 'deepstate-react';
9
+ * import { useSelect } from 'deepstate-react';
10
10
  *
11
11
  * const store = state({ user: { name: 'Alice', age: 30 }, count: 0 });
12
12
  *
13
+ * // Get raw value
13
14
  * function UserName() {
14
- * const name = useStateValue(store.user.name);
15
+ * const name = useSelect(store.user.name);
15
16
  * return <span>{name}</span>;
16
17
  * }
17
18
  *
19
+ * // With selector
18
20
  * function UserSummary() {
19
- * const summary = useSelector(store.user, user => `${user.name} (${user.age})`);
21
+ * const summary = useSelect(store.user, user => `${user.name} (${user.age})`);
20
22
  * return <span>{summary}</span>;
21
23
  * }
24
+ *
25
+ * // Combine multiple nodes
26
+ * function Progress() {
27
+ * const pct = useSelect(
28
+ * [store.completed, store.total],
29
+ * ([completed, total]) => total > 0 ? (completed / total) * 100 : 0
30
+ * );
31
+ * return <span>{pct}%</span>;
32
+ * }
22
33
  * ```
23
34
  */
24
35
 
25
36
  export {
37
+ useSelect,
38
+ useObservable,
39
+ // Deprecated aliases for backwards compatibility
26
40
  useStateValue,
27
41
  useSelector,
28
- useObservable,
29
42
  } from "./hooks";
30
43
 
31
44
  export type { Observable } from "rxjs";