@montra-interactive/deepstate-react 0.1.4 → 0.2.1

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
@@ -6,7 +6,13 @@ function hasGet(obj) {
6
6
  return obj !== null && typeof obj === "object" && "get" in obj && typeof obj.get === "function";
7
7
  }
8
8
  function isObservable(obj) {
9
- return obj !== null && typeof obj === "object" && "subscribe" in obj && typeof obj.subscribe === "function";
9
+ if (obj === null || typeof obj !== "object")
10
+ return false;
11
+ try {
12
+ return typeof obj.subscribe === "function";
13
+ } catch {
14
+ return false;
15
+ }
10
16
  }
11
17
  function useObservable(observable$, getSnapshot) {
12
18
  const valueRef = useRef(getSnapshot());
@@ -20,27 +26,16 @@ function useObservable(observable$, getSnapshot) {
20
26
  const getSnapshotMemo = useCallback(() => valueRef.current, []);
21
27
  return useSyncExternalStore(subscribe, getSnapshotMemo, getSnapshotMemo);
22
28
  }
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) {
29
+ function useSelect(nodeOrNodes, selector, equalityFn = Object.is) {
36
30
  const { combined$, getInitialValue } = useMemo(() => {
37
31
  if (Array.isArray(nodeOrNodes)) {
38
32
  const nodes = nodeOrNodes;
33
+ const sel = selector;
39
34
  return {
40
- combined$: combineLatest(nodes).pipe(map((values) => selector(values)), distinctUntilChanged(equalityFn)),
35
+ combined$: combineLatest(nodes).pipe(map((values) => sel(values)), distinctUntilChanged(equalityFn)),
41
36
  getInitialValue: () => {
42
37
  const values = nodes.map((n) => hasGet(n) ? n.get() : undefined);
43
- return selector(values);
38
+ return sel(values);
44
39
  }
45
40
  };
46
41
  }
@@ -48,13 +43,14 @@ function useSelector(nodeOrNodes, selector, equalityFn = Object.is) {
48
43
  const obj = nodeOrNodes;
49
44
  const keys = Object.keys(obj);
50
45
  const observables = keys.map((k) => obj[k]);
46
+ const sel = selector;
51
47
  return {
52
48
  combined$: combineLatest(observables).pipe(map((values) => {
53
49
  const result = {};
54
50
  keys.forEach((key, i) => {
55
51
  result[key] = values[i];
56
52
  });
57
- return selector(result);
53
+ return sel(result);
58
54
  }), distinctUntilChanged(equalityFn)),
59
55
  getInitialValue: () => {
60
56
  const result = {};
@@ -62,20 +58,32 @@ function useSelector(nodeOrNodes, selector, equalityFn = Object.is) {
62
58
  const node2 = obj[key];
63
59
  result[key] = hasGet(node2) ? node2.get() : undefined;
64
60
  });
65
- return selector(result);
61
+ return sel(result);
66
62
  }
67
63
  };
68
64
  }
69
65
  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());
66
+ if (selector) {
67
+ return {
68
+ combined$: node.pipe(map((value) => selector(value)), distinctUntilChanged(equalityFn)),
69
+ getInitialValue: () => {
70
+ if (hasGet(node)) {
71
+ return selector(node.get());
72
+ }
73
+ return;
74
+ }
75
+ };
76
+ } else {
77
+ return {
78
+ combined$: node.pipe(distinctUntilChanged(equalityFn)),
79
+ getInitialValue: () => {
80
+ if (hasGet(node)) {
81
+ return node.get();
82
+ }
83
+ return;
75
84
  }
76
- return;
77
- }
78
- };
85
+ };
86
+ }
79
87
  }, [nodeOrNodes, selector, equalityFn]);
80
88
  const valueRef = useRef(getInitialValue());
81
89
  const subscribe = useCallback((onStoreChange) => {
@@ -88,8 +96,11 @@ function useSelector(nodeOrNodes, selector, equalityFn = Object.is) {
88
96
  const getSnapshot = useCallback(() => valueRef.current, []);
89
97
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
90
98
  }
99
+ var useStateValue = useSelect;
100
+ var useSelector = useSelect;
91
101
  export {
92
102
  useStateValue,
93
103
  useSelector,
104
+ useSelect,
94
105
  useObservable
95
106
  };
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.1",
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
@@ -36,12 +36,14 @@ function hasGet<T>(obj: unknown): obj is NodeWithGet<T> {
36
36
  }
37
37
 
38
38
  function isObservable(obj: unknown): obj is Observable<unknown> {
39
- return (
40
- obj !== null &&
41
- typeof obj === "object" &&
42
- "subscribe" in obj &&
43
- typeof (obj as Record<string, unknown>).subscribe === "function"
44
- );
39
+ if (obj === null || typeof obj !== "object") return false;
40
+ // Check by accessing subscribe directly - works with proxied observables
41
+ // where "in" operator may not work correctly
42
+ try {
43
+ return typeof (obj as Record<string, unknown>).subscribe === "function";
44
+ } catch {
45
+ return false;
46
+ }
45
47
  }
46
48
 
47
49
  /**
@@ -91,107 +93,46 @@ export function useObservable<T>(
91
93
  }
92
94
 
93
95
  /**
94
- * Hook to get the current value of a deepstate node.
95
- * Re-renders the component whenever the node's value changes.
96
+ * Hook to get values from one or more deepstate nodes, optionally with a selector function.
97
+ * Re-renders the component whenever the selected value changes.
96
98
  *
97
99
  * This is the primary hook for using deepstate in React.
98
- * Works with any deepstate node: RxLeaf, RxObject, RxArray, or RxNullable.
99
- *
100
100
  * Uses React 18's useSyncExternalStore for concurrent-mode safety.
101
101
  *
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
102
+ * @example Single node (get raw value)
106
103
  * ```tsx
107
104
  * import { state } from 'deepstate';
108
- * import { useStateValue } from 'deepstate-react';
105
+ * import { useSelect } from 'deepstate-react';
109
106
  *
110
107
  * const store = state({
111
108
  * user: { name: 'Alice', age: 30 },
112
- * items: [{ id: 1, name: 'Item 1' }],
113
109
  * count: 0
114
110
  * });
115
111
  *
116
112
  * // Subscribe to a primitive
117
113
  * function Counter() {
118
- * const count = useStateValue(store.count);
114
+ * const count = useSelect(store.count);
119
115
  * return <span>{count}</span>;
120
116
  * }
121
117
  *
122
118
  * // Subscribe to an object
123
119
  * function UserCard() {
124
- * const user = useStateValue(store.user);
120
+ * const user = useSelect(store.user);
125
121
  * return <div>{user.name}, {user.age}</div>;
126
122
  * }
127
123
  *
128
124
  * // Subscribe to a nested property (fine-grained!)
129
125
  * function UserName() {
130
- * const name = useStateValue(store.user.name);
126
+ * const name = useSelect(store.user.name);
131
127
  * return <span>{name}</span>;
132
128
  * }
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
129
  * ```
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
130
  *
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
131
+ * @example Single node with selector (derive a value)
183
132
  * ```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
133
  * // Derive a computed value from a single node
193
134
  * function FullName() {
194
- * const fullName = useSelector(
135
+ * const fullName = useSelect(
195
136
  * store.user,
196
137
  * user => `${user.firstName} ${user.lastName}`
197
138
  * );
@@ -203,7 +144,7 @@ export function useStateValue<T extends Observable<unknown>>(
203
144
  * ```tsx
204
145
  * // Combine multiple nodes - receives values as tuple
205
146
  * function Progress() {
206
- * const percentage = useSelector(
147
+ * const percentage = useSelect(
207
148
  * [store.completed, store.total],
208
149
  * ([completed, total]) => total > 0 ? (completed / total) * 100 : 0
209
150
  * );
@@ -215,7 +156,7 @@ export function useStateValue<T extends Observable<unknown>>(
215
156
  * ```tsx
216
157
  * // Combine multiple nodes - receives values as object
217
158
  * function Progress() {
218
- * const percentage = useSelector(
159
+ * const percentage = useSelect(
219
160
  * { completed: store.completed, total: store.total },
220
161
  * ({ completed, total }) => total > 0 ? (completed / total) * 100 : 0
221
162
  * );
@@ -226,7 +167,7 @@ export function useStateValue<T extends Observable<unknown>>(
226
167
  * @example With custom equality
227
168
  * ```tsx
228
169
  * function ItemIds() {
229
- * const ids = useSelector(
170
+ * const ids = useSelect(
230
171
  * store.items,
231
172
  * items => items.map(i => i.id),
232
173
  * (a, b) => a.length === b.length && a.every((v, i) => v === b[i])
@@ -235,14 +176,18 @@ export function useStateValue<T extends Observable<unknown>>(
235
176
  * }
236
177
  * ```
237
178
  */
238
- // Single node overload
239
- export function useSelector<T extends Observable<unknown>, R>(
179
+ // Single node, no selector - return raw value
180
+ export function useSelect<T extends Observable<unknown>>(
181
+ node: T
182
+ ): ObservableValue<T>;
183
+ // Single node with selector
184
+ export function useSelect<T extends Observable<unknown>, R>(
240
185
  node: T,
241
186
  selector: (value: ObservableValue<T>) => R,
242
187
  equalityFn?: (a: R, b: R) => boolean
243
188
  ): R;
244
- // Array of nodes overload
245
- export function useSelector<
189
+ // Array of 2 nodes with selector
190
+ export function useSelect<
246
191
  T1 extends Observable<unknown>,
247
192
  T2 extends Observable<unknown>,
248
193
  R
@@ -251,7 +196,8 @@ export function useSelector<
251
196
  selector: (values: [ObservableValue<T1>, ObservableValue<T2>]) => R,
252
197
  equalityFn?: (a: R, b: R) => boolean
253
198
  ): R;
254
- export function useSelector<
199
+ // Array of 3 nodes with selector
200
+ export function useSelect<
255
201
  T1 extends Observable<unknown>,
256
202
  T2 extends Observable<unknown>,
257
203
  T3 extends Observable<unknown>,
@@ -261,7 +207,8 @@ export function useSelector<
261
207
  selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>]) => R,
262
208
  equalityFn?: (a: R, b: R) => boolean
263
209
  ): R;
264
- export function useSelector<
210
+ // Array of 4 nodes with selector
211
+ export function useSelect<
265
212
  T1 extends Observable<unknown>,
266
213
  T2 extends Observable<unknown>,
267
214
  T3 extends Observable<unknown>,
@@ -272,7 +219,8 @@ export function useSelector<
272
219
  selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>, ObservableValue<T4>]) => R,
273
220
  equalityFn?: (a: R, b: R) => boolean
274
221
  ): R;
275
- export function useSelector<
222
+ // Array of 5 nodes with selector
223
+ export function useSelect<
276
224
  T1 extends Observable<unknown>,
277
225
  T2 extends Observable<unknown>,
278
226
  T3 extends Observable<unknown>,
@@ -284,42 +232,44 @@ export function useSelector<
284
232
  selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>, ObservableValue<T4>, ObservableValue<T5>]) => R,
285
233
  equalityFn?: (a: R, b: R) => boolean
286
234
  ): R;
287
- // Object of nodes overload
288
- export function useSelector<T extends Record<string, Observable<unknown>>, R>(
235
+ // Object of nodes with selector
236
+ export function useSelect<T extends Record<string, Observable<unknown>>, R>(
289
237
  nodes: T,
290
238
  selector: (values: ObservableObjectValues<T>) => R,
291
239
  equalityFn?: (a: R, b: R) => boolean
292
240
  ): R;
293
241
  // Implementation
294
- export function useSelector(
242
+ export function useSelect(
295
243
  nodeOrNodes: Observable<unknown> | Observable<unknown>[] | Record<string, Observable<unknown>>,
296
244
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
297
- selector: (value: any) => any,
245
+ selector?: (value: any) => any,
298
246
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
299
247
  equalityFn: (a: any, b: any) => boolean = Object.is
300
248
  ): unknown {
301
249
  // Determine the form and create the combined observable
302
250
  const { combined$, getInitialValue } = useMemo(() => {
303
- // Array form: [node1, node2, ...]
251
+ // Array form: [node1, node2, ...] - always requires selector
304
252
  if (Array.isArray(nodeOrNodes)) {
305
253
  const nodes = nodeOrNodes as Observable<unknown>[];
254
+ const sel = selector!; // selector is required for array form
306
255
  return {
307
256
  combined$: combineLatest(nodes).pipe(
308
- map((values) => selector(values)),
257
+ map((values) => sel(values)),
309
258
  distinctUntilChanged(equalityFn)
310
259
  ),
311
260
  getInitialValue: (): unknown => {
312
261
  const values = nodes.map((n) => (hasGet<unknown>(n) ? n.get() : undefined));
313
- return selector(values);
262
+ return sel(values);
314
263
  },
315
264
  };
316
265
  }
317
266
 
318
- // Object form: { a: node1, b: node2, ... }
267
+ // Object form: { a: node1, b: node2, ... } - always requires selector
319
268
  if (!isObservable(nodeOrNodes)) {
320
269
  const obj = nodeOrNodes as Record<string, Observable<unknown>>;
321
270
  const keys = Object.keys(obj);
322
271
  const observables = keys.map((k) => obj[k]);
272
+ const sel = selector!; // selector is required for object form
323
273
 
324
274
  return {
325
275
  combined$: combineLatest(observables).pipe(
@@ -328,7 +278,7 @@ export function useSelector(
328
278
  keys.forEach((key, i) => {
329
279
  result[key] = values[i];
330
280
  });
331
- return selector(result);
281
+ return sel(result);
332
282
  }),
333
283
  distinctUntilChanged(equalityFn)
334
284
  ),
@@ -338,25 +288,42 @@ export function useSelector(
338
288
  const node = obj[key];
339
289
  result[key] = hasGet<unknown>(node) ? node.get() : undefined;
340
290
  });
341
- return selector(result);
291
+ return sel(result);
342
292
  },
343
293
  };
344
294
  }
345
295
 
346
- // Single node form
296
+ // Single node form - selector is optional
347
297
  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
- };
298
+
299
+ if (selector) {
300
+ // With selector - apply transformation
301
+ return {
302
+ combined$: node.pipe(
303
+ map((value) => selector(value)),
304
+ distinctUntilChanged(equalityFn)
305
+ ),
306
+ getInitialValue: (): unknown => {
307
+ if (hasGet<unknown>(node)) {
308
+ return selector(node.get());
309
+ }
310
+ return undefined;
311
+ },
312
+ };
313
+ } else {
314
+ // No selector - return raw value
315
+ return {
316
+ combined$: node.pipe(
317
+ distinctUntilChanged(equalityFn)
318
+ ),
319
+ getInitialValue: (): unknown => {
320
+ if (hasGet<unknown>(node)) {
321
+ return node.get();
322
+ }
323
+ return undefined;
324
+ },
325
+ };
326
+ }
360
327
  }, [nodeOrNodes, selector, equalityFn]);
361
328
 
362
329
  // Ref to hold the current derived value
@@ -380,3 +347,13 @@ export function useSelector(
380
347
 
381
348
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
382
349
  }
350
+
351
+ /**
352
+ * @deprecated Use `useSelect` instead. This is an alias for backwards compatibility.
353
+ */
354
+ export const useStateValue = useSelect;
355
+
356
+ /**
357
+ * @deprecated Use `useSelect` instead. This is an alias for backwards compatibility.
358
+ */
359
+ 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";