@montra-interactive/deepstate-react 0.2.3 → 0.2.4

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
@@ -1,15 +1,16 @@
1
1
  import type { Observable } from "rxjs";
2
2
  /**
3
- * Type helper to extract the value type from an Observable.
4
- * Works with deepstate nodes since they extend Observable.
3
+ * Interface for deepstate nodes that have a synchronous get() method.
4
+ * This is used internally to detect deepstate nodes vs plain observables.
5
5
  */
6
- type ObservableValue<T> = T extends Observable<infer V> ? V : never;
6
+ interface NodeWithGet<T> {
7
+ get(): T;
8
+ }
7
9
  /**
8
- * Type for object of observables -> object of their values
10
+ * A deepstate node - an Observable that also has a synchronous get() method.
11
+ * Used to enforce that useSelect only accepts deepstate nodes, not piped observables.
9
12
  */
10
- type ObservableObjectValues<T extends Record<string, Observable<unknown>>> = {
11
- [K in keyof T]: ObservableValue<T[K]>;
12
- };
13
+ export type DeepstateNode<T> = Observable<T> & NodeWithGet<T>;
13
14
  /**
14
15
  * Hook to subscribe to any Observable and get its current value.
15
16
  * Re-renders the component whenever the observable emits a new value.
@@ -118,13 +119,15 @@ export declare function useObservable<T>(observable$: Observable<T>, getSnapshot
118
119
  * }
119
120
  * ```
120
121
  */
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;
122
+ export declare function useSelect<T>(node: DeepstateNode<T>): T;
123
+ export declare function useSelect<T, R>(node: DeepstateNode<T>, selector: (value: T) => R, equalityFn?: (a: R, b: R) => boolean): R;
124
+ export declare function useSelect<T1, T2, R>(nodes: [DeepstateNode<T1>, DeepstateNode<T2>], selector: (values: [T1, T2]) => R, equalityFn?: (a: R, b: R) => boolean): R;
125
+ export declare function useSelect<T1, T2, T3, R>(nodes: [DeepstateNode<T1>, DeepstateNode<T2>, DeepstateNode<T3>], selector: (values: [T1, T2, T3]) => R, equalityFn?: (a: R, b: R) => boolean): R;
126
+ export declare function useSelect<T1, T2, T3, T4, R>(nodes: [DeepstateNode<T1>, DeepstateNode<T2>, DeepstateNode<T3>, DeepstateNode<T4>], selector: (values: [T1, T2, T3, T4]) => R, equalityFn?: (a: R, b: R) => boolean): R;
127
+ export declare function useSelect<T1, T2, T3, T4, T5, R>(nodes: [DeepstateNode<T1>, DeepstateNode<T2>, DeepstateNode<T3>, DeepstateNode<T4>, DeepstateNode<T5>], selector: (values: [T1, T2, T3, T4, T5]) => R, equalityFn?: (a: R, b: R) => boolean): R;
128
+ export declare function useSelect<T extends Record<string, DeepstateNode<unknown>>, R>(nodes: T, selector: (values: {
129
+ [K in keyof T]: T[K] extends DeepstateNode<infer V> ? V : never;
130
+ }) => R, equalityFn?: (a: R, b: R) => boolean): R;
128
131
  /**
129
132
  * @deprecated Use `useSelect` instead. This is an alias for backwards compatibility.
130
133
  */
@@ -133,5 +136,79 @@ export declare const useStateValue: typeof useSelect;
133
136
  * @deprecated Use `useSelect` instead. This is an alias for backwards compatibility.
134
137
  */
135
138
  export declare const useSelector: typeof useSelect;
139
+ /**
140
+ * Hook to subscribe to a piped observable stream.
141
+ * Unlike `useSelect`, this hook is designed for observables that have been transformed
142
+ * with RxJS operators like `filter`, `debounceTime`, `map`, etc.
143
+ *
144
+ * Since piped observables don't have a synchronous `.get()` method, the initial value
145
+ * is `undefined` until the first emission occurs.
146
+ *
147
+ * @param piped$ - An RxJS Observable (typically created by calling .pipe() on a deepstate node)
148
+ * @returns The current value from the stream, or `undefined` if no value has been emitted yet
149
+ *
150
+ * @example Basic usage with filter
151
+ * ```tsx
152
+ * import { usePipeSelect } from '@montra-interactive/deepstate-react';
153
+ * import { filter } from 'rxjs';
154
+ *
155
+ * function OnlyPositive() {
156
+ * // Will be undefined until a value > 0 is emitted
157
+ * const value = usePipeSelect(store.count.pipe(filter(v => v > 0)));
158
+ *
159
+ * if (value === undefined) {
160
+ * return <span>Waiting for positive value...</span>;
161
+ * }
162
+ * return <span>{value}</span>;
163
+ * }
164
+ * ```
165
+ *
166
+ * @example Debouncing high-frequency updates
167
+ * ```tsx
168
+ * import { usePipeSelect } from '@montra-interactive/deepstate-react';
169
+ * import { debounceTime } from 'rxjs';
170
+ *
171
+ * function DebouncedInput() {
172
+ * // Reduces re-renders by debouncing updates
173
+ * const searchTerm = usePipeSelect(store.searchInput.pipe(debounceTime(300)));
174
+ *
175
+ * return <span>Searching for: {searchTerm ?? 'nothing yet'}</span>;
176
+ * }
177
+ * ```
178
+ *
179
+ * @example Mapping values
180
+ * ```tsx
181
+ * import { usePipeSelect } from '@montra-interactive/deepstate-react';
182
+ * import { map } from 'rxjs';
183
+ *
184
+ * function ItemCount() {
185
+ * const count = usePipeSelect(store.items.pipe(map(items => items.length)));
186
+ *
187
+ * return <span>Count: {count ?? 0}</span>;
188
+ * }
189
+ * ```
190
+ *
191
+ * @example Combining operators
192
+ * ```tsx
193
+ * import { usePipeSelect } from '@montra-interactive/deepstate-react';
194
+ * import { filter, debounceTime, distinctUntilChanged } from 'rxjs';
195
+ *
196
+ * function FilteredSearch() {
197
+ * const results = usePipeSelect(
198
+ * store.searchResults.pipe(
199
+ * filter(r => r.length > 0),
200
+ * debounceTime(200),
201
+ * distinctUntilChanged()
202
+ * )
203
+ * );
204
+ *
205
+ * if (results === undefined) {
206
+ * return <span>No results yet</span>;
207
+ * }
208
+ * return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>;
209
+ * }
210
+ * ```
211
+ */
212
+ export declare function usePipeSelect<T>(piped$: Observable<T>): T | undefined;
136
213
  export {};
137
214
  //# 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
@@ -32,6 +32,7 @@
32
32
  * }
33
33
  * ```
34
34
  */
35
- export { useSelect, useObservable, useStateValue, useSelector, } from "./hooks";
35
+ export { useSelect, usePipeSelect, useObservable, useStateValue, useSelector, } from "./hooks";
36
+ export type { DeepstateNode } from "./hooks";
36
37
  export type { Observable } from "rxjs";
37
38
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,SAAS,EACT,aAAa,EACb,aAAa,EAEb,aAAa,EACb,WAAW,GACZ,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -104,9 +104,24 @@ function useSelect(nodeOrNodes, selector, equalityFn = Object.is) {
104
104
  }
105
105
  var useStateValue = useSelect;
106
106
  var useSelector = useSelect;
107
+ function usePipeSelect(piped$) {
108
+ const hasValueRef = useRef(false);
109
+ const valueRef = useRef(undefined);
110
+ const subscribe = useCallback((onStoreChange) => {
111
+ const subscription = piped$.subscribe((newValue) => {
112
+ hasValueRef.current = true;
113
+ valueRef.current = newValue;
114
+ onStoreChange();
115
+ });
116
+ return () => subscription.unsubscribe();
117
+ }, [piped$]);
118
+ const getSnapshot = useCallback(() => valueRef.current, []);
119
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
120
+ }
107
121
  export {
108
122
  useStateValue,
109
123
  useSelector,
110
124
  useSelect,
125
+ usePipeSelect,
111
126
  useObservable
112
127
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@montra-interactive/deepstate-react",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
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
@@ -31,6 +31,12 @@ interface NodeWithGet<T> {
31
31
  get(): T;
32
32
  }
33
33
 
34
+ /**
35
+ * A deepstate node - an Observable that also has a synchronous get() method.
36
+ * Used to enforce that useSelect only accepts deepstate nodes, not piped observables.
37
+ */
38
+ export type DeepstateNode<T> = Observable<T> & NodeWithGet<T>;
39
+
34
40
  function hasGet<T>(obj: unknown): obj is NodeWithGet<T> {
35
41
  if (obj === null || typeof obj !== "object") return false;
36
42
  // Check by accessing get directly - works with proxied observables
@@ -184,65 +190,45 @@ export function useObservable<T>(
184
190
  * ```
185
191
  */
186
192
  // Single node, no selector - return raw value
187
- export function useSelect<T extends Observable<unknown>>(
188
- node: T
189
- ): ObservableValue<T>;
193
+ // Note: Requires a deepstate node (with .get()), not a piped observable.
194
+ // Use usePipeSelect for piped observables.
195
+ export function useSelect<T>(
196
+ node: DeepstateNode<T>
197
+ ): T;
190
198
  // Single node with selector
191
- export function useSelect<T extends Observable<unknown>, R>(
192
- node: T,
193
- selector: (value: ObservableValue<T>) => R,
199
+ export function useSelect<T, R>(
200
+ node: DeepstateNode<T>,
201
+ selector: (value: T) => R,
194
202
  equalityFn?: (a: R, b: R) => boolean
195
203
  ): R;
196
204
  // Array of 2 nodes with selector
197
- export function useSelect<
198
- T1 extends Observable<unknown>,
199
- T2 extends Observable<unknown>,
200
- R
201
- >(
202
- nodes: [T1, T2],
203
- selector: (values: [ObservableValue<T1>, ObservableValue<T2>]) => R,
205
+ export function useSelect<T1, T2, R>(
206
+ nodes: [DeepstateNode<T1>, DeepstateNode<T2>],
207
+ selector: (values: [T1, T2]) => R,
204
208
  equalityFn?: (a: R, b: R) => boolean
205
209
  ): R;
206
210
  // Array of 3 nodes with selector
207
- export function useSelect<
208
- T1 extends Observable<unknown>,
209
- T2 extends Observable<unknown>,
210
- T3 extends Observable<unknown>,
211
- R
212
- >(
213
- nodes: [T1, T2, T3],
214
- selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>]) => R,
211
+ export function useSelect<T1, T2, T3, R>(
212
+ nodes: [DeepstateNode<T1>, DeepstateNode<T2>, DeepstateNode<T3>],
213
+ selector: (values: [T1, T2, T3]) => R,
215
214
  equalityFn?: (a: R, b: R) => boolean
216
215
  ): R;
217
216
  // Array of 4 nodes with selector
218
- export function useSelect<
219
- T1 extends Observable<unknown>,
220
- T2 extends Observable<unknown>,
221
- T3 extends Observable<unknown>,
222
- T4 extends Observable<unknown>,
223
- R
224
- >(
225
- nodes: [T1, T2, T3, T4],
226
- selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>, ObservableValue<T4>]) => R,
217
+ export function useSelect<T1, T2, T3, T4, R>(
218
+ nodes: [DeepstateNode<T1>, DeepstateNode<T2>, DeepstateNode<T3>, DeepstateNode<T4>],
219
+ selector: (values: [T1, T2, T3, T4]) => R,
227
220
  equalityFn?: (a: R, b: R) => boolean
228
221
  ): R;
229
222
  // Array of 5 nodes with selector
230
- export function useSelect<
231
- T1 extends Observable<unknown>,
232
- T2 extends Observable<unknown>,
233
- T3 extends Observable<unknown>,
234
- T4 extends Observable<unknown>,
235
- T5 extends Observable<unknown>,
236
- R
237
- >(
238
- nodes: [T1, T2, T3, T4, T5],
239
- selector: (values: [ObservableValue<T1>, ObservableValue<T2>, ObservableValue<T3>, ObservableValue<T4>, ObservableValue<T5>]) => R,
223
+ export function useSelect<T1, T2, T3, T4, T5, R>(
224
+ nodes: [DeepstateNode<T1>, DeepstateNode<T2>, DeepstateNode<T3>, DeepstateNode<T4>, DeepstateNode<T5>],
225
+ selector: (values: [T1, T2, T3, T4, T5]) => R,
240
226
  equalityFn?: (a: R, b: R) => boolean
241
227
  ): R;
242
228
  // Object of nodes with selector
243
- export function useSelect<T extends Record<string, Observable<unknown>>, R>(
229
+ export function useSelect<T extends Record<string, DeepstateNode<unknown>>, R>(
244
230
  nodes: T,
245
- selector: (values: ObservableObjectValues<T>) => R,
231
+ selector: (values: { [K in keyof T]: T[K] extends DeepstateNode<infer V> ? V : never }) => R,
246
232
  equalityFn?: (a: R, b: R) => boolean
247
233
  ): R;
248
234
  // Implementation
@@ -364,3 +350,99 @@ export const useStateValue = useSelect;
364
350
  * @deprecated Use `useSelect` instead. This is an alias for backwards compatibility.
365
351
  */
366
352
  export const useSelector = useSelect;
353
+
354
+ /**
355
+ * Hook to subscribe to a piped observable stream.
356
+ * Unlike `useSelect`, this hook is designed for observables that have been transformed
357
+ * with RxJS operators like `filter`, `debounceTime`, `map`, etc.
358
+ *
359
+ * Since piped observables don't have a synchronous `.get()` method, the initial value
360
+ * is `undefined` until the first emission occurs.
361
+ *
362
+ * @param piped$ - An RxJS Observable (typically created by calling .pipe() on a deepstate node)
363
+ * @returns The current value from the stream, or `undefined` if no value has been emitted yet
364
+ *
365
+ * @example Basic usage with filter
366
+ * ```tsx
367
+ * import { usePipeSelect } from '@montra-interactive/deepstate-react';
368
+ * import { filter } from 'rxjs';
369
+ *
370
+ * function OnlyPositive() {
371
+ * // Will be undefined until a value > 0 is emitted
372
+ * const value = usePipeSelect(store.count.pipe(filter(v => v > 0)));
373
+ *
374
+ * if (value === undefined) {
375
+ * return <span>Waiting for positive value...</span>;
376
+ * }
377
+ * return <span>{value}</span>;
378
+ * }
379
+ * ```
380
+ *
381
+ * @example Debouncing high-frequency updates
382
+ * ```tsx
383
+ * import { usePipeSelect } from '@montra-interactive/deepstate-react';
384
+ * import { debounceTime } from 'rxjs';
385
+ *
386
+ * function DebouncedInput() {
387
+ * // Reduces re-renders by debouncing updates
388
+ * const searchTerm = usePipeSelect(store.searchInput.pipe(debounceTime(300)));
389
+ *
390
+ * return <span>Searching for: {searchTerm ?? 'nothing yet'}</span>;
391
+ * }
392
+ * ```
393
+ *
394
+ * @example Mapping values
395
+ * ```tsx
396
+ * import { usePipeSelect } from '@montra-interactive/deepstate-react';
397
+ * import { map } from 'rxjs';
398
+ *
399
+ * function ItemCount() {
400
+ * const count = usePipeSelect(store.items.pipe(map(items => items.length)));
401
+ *
402
+ * return <span>Count: {count ?? 0}</span>;
403
+ * }
404
+ * ```
405
+ *
406
+ * @example Combining operators
407
+ * ```tsx
408
+ * import { usePipeSelect } from '@montra-interactive/deepstate-react';
409
+ * import { filter, debounceTime, distinctUntilChanged } from 'rxjs';
410
+ *
411
+ * function FilteredSearch() {
412
+ * const results = usePipeSelect(
413
+ * store.searchResults.pipe(
414
+ * filter(r => r.length > 0),
415
+ * debounceTime(200),
416
+ * distinctUntilChanged()
417
+ * )
418
+ * );
419
+ *
420
+ * if (results === undefined) {
421
+ * return <span>No results yet</span>;
422
+ * }
423
+ * return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>;
424
+ * }
425
+ * ```
426
+ */
427
+ export function usePipeSelect<T>(piped$: Observable<T>): T | undefined {
428
+ // Track whether we've received a value yet
429
+ const hasValueRef = useRef(false);
430
+ const valueRef = useRef<T | undefined>(undefined);
431
+
432
+ const subscribe = useCallback(
433
+ (onStoreChange: () => void) => {
434
+ const subscription = piped$.subscribe((newValue) => {
435
+ hasValueRef.current = true;
436
+ valueRef.current = newValue;
437
+ onStoreChange();
438
+ });
439
+
440
+ return () => subscription.unsubscribe();
441
+ },
442
+ [piped$]
443
+ );
444
+
445
+ const getSnapshot = useCallback(() => valueRef.current, []);
446
+
447
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
448
+ }
package/src/index.ts CHANGED
@@ -35,10 +35,13 @@
35
35
 
36
36
  export {
37
37
  useSelect,
38
+ usePipeSelect,
38
39
  useObservable,
39
40
  // Deprecated aliases for backwards compatibility
40
41
  useStateValue,
41
42
  useSelector,
42
43
  } from "./hooks";
43
44
 
45
+ export type { DeepstateNode } from "./hooks";
46
+
44
47
  export type { Observable } from "rxjs";