@ic-reactor/react 3.9.0 → 3.11.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.
Files changed (61) hide show
  1. package/README.md +16 -7
  2. package/dist/auth/authentication-manager.d.ts.map +1 -1
  3. package/dist/auth/authentication-manager.js +9 -0
  4. package/dist/auth/authentication-manager.js.map +1 -1
  5. package/dist/auth/createIdentityAttributeHooks.d.ts.map +1 -1
  6. package/dist/auth/createIdentityAttributeHooks.js +47 -5
  7. package/dist/auth/createIdentityAttributeHooks.js.map +1 -1
  8. package/dist/createMutation.d.ts.map +1 -1
  9. package/dist/createMutation.js +15 -1
  10. package/dist/createMutation.js.map +1 -1
  11. package/dist/createQuery.js +2 -2
  12. package/dist/createQuery.js.map +1 -1
  13. package/dist/createSuspenseQuery.js +2 -2
  14. package/dist/createSuspenseQuery.js.map +1 -1
  15. package/dist/defineReactor.d.ts.map +1 -1
  16. package/dist/defineReactor.js +17 -2
  17. package/dist/defineReactor.js.map +1 -1
  18. package/dist/hooks/createAuthHooks.d.ts.map +1 -1
  19. package/dist/hooks/createAuthHooks.js +13 -0
  20. package/dist/hooks/createAuthHooks.js.map +1 -1
  21. package/dist/hooks/index.d.ts.map +1 -1
  22. package/dist/hooks/index.js +6 -0
  23. package/dist/hooks/index.js.map +1 -1
  24. package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
  25. package/dist/hooks/useActorInfiniteQuery.js +5 -0
  26. package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
  27. package/dist/hooks/useActorMethod.d.ts.map +1 -1
  28. package/dist/hooks/useActorMethod.js +11 -1
  29. package/dist/hooks/useActorMethod.js.map +1 -1
  30. package/dist/hooks/useActorMutation.d.ts.map +1 -1
  31. package/dist/hooks/useActorMutation.js +14 -6
  32. package/dist/hooks/useActorMutation.js.map +1 -1
  33. package/dist/hooks/useActorQuery.d.ts.map +1 -1
  34. package/dist/hooks/useActorQuery.js +12 -1
  35. package/dist/hooks/useActorQuery.js.map +1 -1
  36. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
  37. package/dist/hooks/useActorSuspenseInfiniteQuery.js +5 -0
  38. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
  39. package/dist/hooks/useActorSuspenseQuery.d.ts.map +1 -1
  40. package/dist/hooks/useActorSuspenseQuery.js +12 -1
  41. package/dist/hooks/useActorSuspenseQuery.js.map +1 -1
  42. package/dist/utils.d.ts +13 -0
  43. package/dist/utils.d.ts.map +1 -1
  44. package/dist/utils.js +45 -0
  45. package/dist/utils.js.map +1 -1
  46. package/package.json +8 -2
  47. package/src/auth/authentication-manager.ts +10 -0
  48. package/src/auth/createIdentityAttributeHooks.ts +54 -5
  49. package/src/createMutation.ts +16 -0
  50. package/src/createQuery.ts +9 -9
  51. package/src/createSuspenseQuery.ts +9 -9
  52. package/src/defineReactor.ts +24 -2
  53. package/src/hooks/createAuthHooks.ts +18 -0
  54. package/src/hooks/index.ts +6 -0
  55. package/src/hooks/useActorInfiniteQuery.ts +5 -0
  56. package/src/hooks/useActorMethod.ts +10 -1
  57. package/src/hooks/useActorMutation.ts +16 -8
  58. package/src/hooks/useActorQuery.ts +11 -1
  59. package/src/hooks/useActorSuspenseInfiniteQuery.ts +5 -0
  60. package/src/hooks/useActorSuspenseQuery.ts +11 -1
  61. package/src/utils.ts +45 -0
@@ -1,5 +1,11 @@
1
1
  // ============================================================================
2
2
  // Hooks
3
+ //
4
+ // These are the RAW hooks: they take the reactor as a property. They are
5
+ // therefore exported as `useReactor*` only — the `useActor*` names belong to
6
+ // the bound hooks `createActorHooks` returns, which take no reactor. Aliasing
7
+ // the raw implementations under the bound names would hand a caller following
8
+ // the documented `useActorQuery({ functionName })` shape an undefined reactor.
3
9
  // ============================================================================
4
10
 
5
11
  // useActorQuery / useReactorQuery
@@ -138,6 +138,11 @@ export const useActorInfiniteQuery = <
138
138
  [
139
139
  queryKey,
140
140
  reactor,
141
+ // `canisterId` is mutable reactor state that `setCanisterId` can change,
142
+ // while `reactor` itself stays the same object — so it has to be a
143
+ // dependency in its own right or the key stays pinned to the old canister
144
+ // while the queryFn already calls the new one.
145
+ reactor.canisterId?.toString(),
141
146
  functionName,
142
147
  callConfig,
143
148
  getArgs,
@@ -208,7 +208,16 @@ export function useActorMethod<
208
208
  { functionName, args: keyArgs, queryKey: customQueryKey },
209
209
  callConfig
210
210
  ),
211
- [reactor, functionName, callConfig, customQueryKey]
211
+ // `canisterId` is mutable reactor state that `setCanisterId` can change,
212
+ // while `reactor` itself stays the same object — so it has to be a
213
+ // dependency in its own right or the key stays pinned to the old canister.
214
+ [
215
+ reactor,
216
+ reactor.canisterId?.toString(),
217
+ functionName,
218
+ callConfig,
219
+ customQueryKey,
220
+ ]
212
221
  )
213
222
 
214
223
  const queryKey = useMemo(() => buildQueryKey(args), [buildQueryKey, args])
@@ -1,4 +1,4 @@
1
- import { useMemo, useCallback } from "react"
1
+ import { useCallback } from "react"
2
2
  import {
3
3
  useMutation,
4
4
  UseMutationOptions,
@@ -167,16 +167,24 @@ export const useActorMutation = <
167
167
  [onCanisterError, onError]
168
168
  )
169
169
 
170
- const mutationOptions = useMemo(
171
- () => ({
170
+ // Not memoized. The deps could only ever list the values destructured above,
171
+ // never the `options` rest bucket, so everything passed straight through —
172
+ // `onMutate`, `onSettled`, `retry`, `meta`, `gcTime` — was frozen at the first
173
+ // render: `useMutation` calls `observer.setOptions` from an effect keyed on
174
+ // the options identity, so a later render's closures never reached it. A
175
+ // component passing only `onSettled` would report the recipient selected at
176
+ // mount rather than at submit.
177
+ //
178
+ // `createMutation`'s own hook builds its options inline for the same reason.
179
+ // A fresh object per render costs nothing here: `setOptions` diffs the values
180
+ // rather than the reference.
181
+ return useMutation(
182
+ {
172
183
  ...options,
173
184
  mutationFn,
174
185
  onSuccess: handleSuccess,
175
186
  onError: handleError,
176
- }),
177
- // eslint-disable-next-line react-hooks/exhaustive-deps
178
- [mutationFn, handleSuccess, handleError]
187
+ },
188
+ reactor.queryClient
179
189
  )
180
-
181
- return useMutation(mutationOptions, reactor.queryClient)
182
190
  }
@@ -100,7 +100,17 @@ export const useActorQuery = <
100
100
  args,
101
101
  queryKey: defaultQueryKey,
102
102
  }),
103
- [reactor, callConfig, functionName, args, defaultQueryKey]
103
+ // `canisterId` is mutable reactor state that `setCanisterId` can change,
104
+ // while `reactor` itself stays the same object — so it has to be a
105
+ // dependency in its own right or the key stays pinned to the old canister.
106
+ [
107
+ reactor,
108
+ reactor.canisterId?.toString(),
109
+ callConfig,
110
+ functionName,
111
+ args,
112
+ defaultQueryKey,
113
+ ]
104
114
  )
105
115
 
106
116
  return useQuery(
@@ -148,6 +148,11 @@ export const useActorSuspenseInfiniteQuery = <
148
148
  [
149
149
  queryKey,
150
150
  reactor,
151
+ // `canisterId` is mutable reactor state that `setCanisterId` can change,
152
+ // while `reactor` itself stays the same object — so it has to be a
153
+ // dependency in its own right or the key stays pinned to the old canister
154
+ // while the queryFn already calls the new one.
155
+ reactor.canisterId?.toString(),
151
156
  functionName,
152
157
  callConfig,
153
158
  getArgs,
@@ -104,7 +104,17 @@ export const useActorSuspenseQuery = <
104
104
  args,
105
105
  queryKey: defaultQueryKey,
106
106
  }),
107
- [reactor, callConfig, functionName, args, defaultQueryKey]
107
+ // `canisterId` is mutable reactor state that `setCanisterId` can change,
108
+ // while `reactor` itself stays the same object — so it has to be a
109
+ // dependency in its own right or the key stays pinned to the old canister.
110
+ [
111
+ reactor,
112
+ reactor.canisterId?.toString(),
113
+ callConfig,
114
+ functionName,
115
+ args,
116
+ defaultQueryKey,
117
+ ]
108
118
  )
109
119
 
110
120
  return useSuspenseQuery(
package/src/utils.ts CHANGED
@@ -74,3 +74,48 @@ export function buildChainedSelect<TData, TSelected, TFinal = TSelected>(
74
74
  if (!hookSelect) return configSelect
75
75
  return (rawData: TData) => hookSelect(configSelect(rawData))
76
76
  }
77
+
78
+ /**
79
+ * How many memoized query objects an args-late factory keeps.
80
+ *
81
+ * The memo exists so `getBalance(sameArgs)` returns the same object twice; it
82
+ * was unbounded, so an open-ended argument space — a per-principal balance in a
83
+ * long-lived SPA, a search-as-you-type filter, a cursor-keyed list — grew it
84
+ * forever (measured at ~2.9 kB per entry, 55.8 MB for 20k). A few hundred
85
+ * covers any realistic working set while capping the worst case.
86
+ */
87
+ const FACTORY_CACHE_LIMIT = 256
88
+
89
+ /**
90
+ * Smallest useful LRU: a `Map` already iterates in insertion order, so
91
+ * re-inserting on read is enough to track recency.
92
+ *
93
+ * Eviction is safe — it costs memoization, never correctness. A query object is
94
+ * a closure over the reactor and config, so one rebuilt after eviction behaves
95
+ * identically; only its reference identity differs.
96
+ */
97
+ export function createBoundedCache<V>(limit: number = FACTORY_CACHE_LIMIT) {
98
+ const entries = new Map<string, V>()
99
+
100
+ return {
101
+ get(key: string): V | undefined {
102
+ const value = entries.get(key)
103
+ if (value === undefined) return undefined
104
+ // Touch: move to the most-recent end.
105
+ entries.delete(key)
106
+ entries.set(key, value)
107
+ return value
108
+ },
109
+ set(key: string, value: V): void {
110
+ if (entries.has(key)) entries.delete(key)
111
+ else if (entries.size >= limit) {
112
+ const oldest = entries.keys().next().value
113
+ if (oldest !== undefined) entries.delete(oldest)
114
+ }
115
+ entries.set(key, value)
116
+ },
117
+ get size(): number {
118
+ return entries.size
119
+ },
120
+ }
121
+ }