@montra-interactive/deepstate-react 0.3.6-alpha.0 → 0.3.6
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 +19 -2
- package/dist/hooks.d.ts +30 -0
- package/dist/hooks.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/hooks.ts +30 -0
package/README.md
CHANGED
|
@@ -103,15 +103,32 @@ const summary = useSelect(
|
|
|
103
103
|
);
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
#### Selector Memoization
|
|
107
|
+
|
|
108
|
+
`useSelect` automatically memoizes selectors: the selector function only re-runs when its input values actually change (compared by reference). This means you can safely return new arrays or objects from selectors without causing infinite re-render loops:
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
// Safe! The selector only re-runs when items or sortBy actually change.
|
|
112
|
+
// Even though .sort() returns a new array, it won't cause infinite emissions.
|
|
113
|
+
const sorted = useSelect(
|
|
114
|
+
[store.items, store.sortBy],
|
|
115
|
+
([items, sortBy]) => Array.from(items).sort((a, b) =>
|
|
116
|
+
sortBy === 'name' ? a.name.localeCompare(b.name) : b.updatedAt - a.updatedAt
|
|
117
|
+
)
|
|
118
|
+
);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
This works like Redux's `createSelector` / Reselect — if the inputs haven't changed, the selector doesn't re-run, preserving the previous output reference.
|
|
122
|
+
|
|
106
123
|
#### Custom Equality Function
|
|
107
124
|
|
|
108
|
-
|
|
125
|
+
For additional control, provide a custom equality check as the third argument. This is evaluated on the selector's **output** and acts as a safety net when different inputs might produce equivalent results:
|
|
109
126
|
|
|
110
127
|
```tsx
|
|
111
128
|
const ids = useSelect(
|
|
112
129
|
store.items,
|
|
113
130
|
items => items.map(i => i.id),
|
|
114
|
-
// Custom array equality
|
|
131
|
+
// Custom array equality on the output
|
|
115
132
|
(a, b) => a.length === b.length && a.every((v, i) => v === b[i])
|
|
116
133
|
);
|
|
117
134
|
```
|
package/dist/hooks.d.ts
CHANGED
|
@@ -42,6 +42,20 @@ export declare function useObservable<T>(observable$: Observable<T>, getSnapshot
|
|
|
42
42
|
* This is the primary hook for using deepstate in React.
|
|
43
43
|
* Uses React 18's useSyncExternalStore for concurrent-mode safety.
|
|
44
44
|
*
|
|
45
|
+
* ## Selector Memoization
|
|
46
|
+
*
|
|
47
|
+
* Selectors are automatically memoized on their inputs, similar to Redux's
|
|
48
|
+
* `createSelector` / Reselect. The selector function only re-executes when
|
|
49
|
+
* input values change by reference. This means selectors that return new
|
|
50
|
+
* arrays or objects (e.g. via `.sort()`, `.filter()`, `.map()`) are safe
|
|
51
|
+
* without needing custom equality functions.
|
|
52
|
+
*
|
|
53
|
+
* Memoization works in two layers:
|
|
54
|
+
* 1. **Input dedup** — `distinctUntilChanged` before the selector prevents
|
|
55
|
+
* re-execution when inputs are referentially identical.
|
|
56
|
+
* 2. **Output dedup** — `distinctUntilChanged(equalityFn)` after the selector
|
|
57
|
+
* catches cases where different inputs produce equivalent outputs.
|
|
58
|
+
*
|
|
45
59
|
* @example Single node (get raw value)
|
|
46
60
|
* ```tsx
|
|
47
61
|
* import { state } from 'deepstate';
|
|
@@ -83,6 +97,22 @@ export declare function useObservable<T>(observable$: Observable<T>, getSnapshot
|
|
|
83
97
|
* }
|
|
84
98
|
* ```
|
|
85
99
|
*
|
|
100
|
+
* @example Selector returning new array (safe - auto-memoized)
|
|
101
|
+
* ```tsx
|
|
102
|
+
* // .sort() returns a new array each time, but the selector only
|
|
103
|
+
* // re-runs when items or sortBy actually change.
|
|
104
|
+
* function SortedItems() {
|
|
105
|
+
* const sorted = useSelect(
|
|
106
|
+
* [store.items, store.sortBy],
|
|
107
|
+
* ([items, sortBy]) =>
|
|
108
|
+
* Array.from(items).sort((a, b) =>
|
|
109
|
+
* sortBy === 'name' ? a.name.localeCompare(b.name) : b.date - a.date
|
|
110
|
+
* ),
|
|
111
|
+
* );
|
|
112
|
+
* return <ItemList items={sorted} />;
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
86
116
|
* @example Multiple nodes (array form)
|
|
87
117
|
* ```tsx
|
|
88
118
|
* // Combine multiple nodes - receives values as tuple
|
package/dist/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAwBvC;;;GAGG;AACH,UAAU,WAAW,CAAC,CAAC;IACrB,GAAG,IAAI,CAAC,CAAC;CACV;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;AAgF9D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC,EAC1B,WAAW,EAAE,MAAM,CAAC,GACnB,CAAC,CAkBH;AAED
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/hooks.ts
CHANGED
|
@@ -168,6 +168,20 @@ export function useObservable<T>(
|
|
|
168
168
|
* This is the primary hook for using deepstate in React.
|
|
169
169
|
* Uses React 18's useSyncExternalStore for concurrent-mode safety.
|
|
170
170
|
*
|
|
171
|
+
* ## Selector Memoization
|
|
172
|
+
*
|
|
173
|
+
* Selectors are automatically memoized on their inputs, similar to Redux's
|
|
174
|
+
* `createSelector` / Reselect. The selector function only re-executes when
|
|
175
|
+
* input values change by reference. This means selectors that return new
|
|
176
|
+
* arrays or objects (e.g. via `.sort()`, `.filter()`, `.map()`) are safe
|
|
177
|
+
* without needing custom equality functions.
|
|
178
|
+
*
|
|
179
|
+
* Memoization works in two layers:
|
|
180
|
+
* 1. **Input dedup** — `distinctUntilChanged` before the selector prevents
|
|
181
|
+
* re-execution when inputs are referentially identical.
|
|
182
|
+
* 2. **Output dedup** — `distinctUntilChanged(equalityFn)` after the selector
|
|
183
|
+
* catches cases where different inputs produce equivalent outputs.
|
|
184
|
+
*
|
|
171
185
|
* @example Single node (get raw value)
|
|
172
186
|
* ```tsx
|
|
173
187
|
* import { state } from 'deepstate';
|
|
@@ -209,6 +223,22 @@ export function useObservable<T>(
|
|
|
209
223
|
* }
|
|
210
224
|
* ```
|
|
211
225
|
*
|
|
226
|
+
* @example Selector returning new array (safe - auto-memoized)
|
|
227
|
+
* ```tsx
|
|
228
|
+
* // .sort() returns a new array each time, but the selector only
|
|
229
|
+
* // re-runs when items or sortBy actually change.
|
|
230
|
+
* function SortedItems() {
|
|
231
|
+
* const sorted = useSelect(
|
|
232
|
+
* [store.items, store.sortBy],
|
|
233
|
+
* ([items, sortBy]) =>
|
|
234
|
+
* Array.from(items).sort((a, b) =>
|
|
235
|
+
* sortBy === 'name' ? a.name.localeCompare(b.name) : b.date - a.date
|
|
236
|
+
* ),
|
|
237
|
+
* );
|
|
238
|
+
* return <ItemList items={sorted} />;
|
|
239
|
+
* }
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
212
242
|
* @example Multiple nodes (array form)
|
|
213
243
|
* ```tsx
|
|
214
244
|
* // Combine multiple nodes - receives values as tuple
|