@ic-reactor/react 3.12.5 → 4.0.0-beta.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.
Files changed (139) hide show
  1. package/README.md +236 -423
  2. package/dist/index.d.ts +259 -12
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +470 -17
  5. package/dist/index.js.map +1 -1
  6. package/llms.txt +82 -54
  7. package/package.json +12 -28
  8. package/src/index.tsx +611 -0
  9. package/dist/auth/authentication-manager.d.ts +0 -123
  10. package/dist/auth/authentication-manager.d.ts.map +0 -1
  11. package/dist/auth/authentication-manager.js +0 -767
  12. package/dist/auth/authentication-manager.js.map +0 -1
  13. package/dist/auth/constants.d.ts +0 -24
  14. package/dist/auth/constants.d.ts.map +0 -1
  15. package/dist/auth/constants.js +0 -24
  16. package/dist/auth/constants.js.map +0 -1
  17. package/dist/auth/createIdentityAttributeHooks.d.ts +0 -14
  18. package/dist/auth/createIdentityAttributeHooks.d.ts.map +0 -1
  19. package/dist/auth/createIdentityAttributeHooks.js +0 -106
  20. package/dist/auth/createIdentityAttributeHooks.js.map +0 -1
  21. package/dist/auth/identity-attributes-manager.d.ts +0 -26
  22. package/dist/auth/identity-attributes-manager.d.ts.map +0 -1
  23. package/dist/auth/identity-attributes-manager.js +0 -107
  24. package/dist/auth/identity-attributes-manager.js.map +0 -1
  25. package/dist/auth/identity-attributes.d.ts +0 -19
  26. package/dist/auth/identity-attributes.d.ts.map +0 -1
  27. package/dist/auth/identity-attributes.js +0 -170
  28. package/dist/auth/identity-attributes.js.map +0 -1
  29. package/dist/auth/index.d.ts +0 -8
  30. package/dist/auth/index.d.ts.map +0 -1
  31. package/dist/auth/index.js +0 -8
  32. package/dist/auth/index.js.map +0 -1
  33. package/dist/auth/local-ii-probe.d.ts +0 -46
  34. package/dist/auth/local-ii-probe.d.ts.map +0 -1
  35. package/dist/auth/local-ii-probe.js +0 -102
  36. package/dist/auth/local-ii-probe.js.map +0 -1
  37. package/dist/auth/types.d.ts +0 -179
  38. package/dist/auth/types.d.ts.map +0 -1
  39. package/dist/auth/types.js +0 -2
  40. package/dist/auth/types.js.map +0 -1
  41. package/dist/createActorHooks.d.ts +0 -52
  42. package/dist/createActorHooks.d.ts.map +0 -1
  43. package/dist/createActorHooks.js +0 -17
  44. package/dist/createActorHooks.js.map +0 -1
  45. package/dist/createInfiniteQuery.d.ts +0 -144
  46. package/dist/createInfiniteQuery.d.ts.map +0 -1
  47. package/dist/createInfiniteQuery.js +0 -174
  48. package/dist/createInfiniteQuery.js.map +0 -1
  49. package/dist/createMutation.d.ts +0 -30
  50. package/dist/createMutation.d.ts.map +0 -1
  51. package/dist/createMutation.js +0 -192
  52. package/dist/createMutation.js.map +0 -1
  53. package/dist/createQuery.d.ts +0 -30
  54. package/dist/createQuery.d.ts.map +0 -1
  55. package/dist/createQuery.js +0 -117
  56. package/dist/createQuery.js.map +0 -1
  57. package/dist/createSuspenseInfiniteQuery.d.ts +0 -147
  58. package/dist/createSuspenseInfiniteQuery.d.ts.map +0 -1
  59. package/dist/createSuspenseInfiniteQuery.js +0 -177
  60. package/dist/createSuspenseInfiniteQuery.js.map +0 -1
  61. package/dist/createSuspenseQuery.d.ts +0 -25
  62. package/dist/createSuspenseQuery.d.ts.map +0 -1
  63. package/dist/createSuspenseQuery.js +0 -111
  64. package/dist/createSuspenseQuery.js.map +0 -1
  65. package/dist/defineReactor.d.ts +0 -125
  66. package/dist/defineReactor.d.ts.map +0 -1
  67. package/dist/defineReactor.js +0 -180
  68. package/dist/defineReactor.js.map +0 -1
  69. package/dist/hooks/createAuthHooks.d.ts +0 -43
  70. package/dist/hooks/createAuthHooks.d.ts.map +0 -1
  71. package/dist/hooks/createAuthHooks.js +0 -131
  72. package/dist/hooks/createAuthHooks.js.map +0 -1
  73. package/dist/hooks/index.d.ts +0 -21
  74. package/dist/hooks/index.d.ts.map +0 -1
  75. package/dist/hooks/index.js +0 -24
  76. package/dist/hooks/index.js.map +0 -1
  77. package/dist/hooks/useActorInfiniteQuery.d.ts +0 -39
  78. package/dist/hooks/useActorInfiniteQuery.d.ts.map +0 -1
  79. package/dist/hooks/useActorInfiniteQuery.js +0 -56
  80. package/dist/hooks/useActorInfiniteQuery.js.map +0 -1
  81. package/dist/hooks/useActorMethod.d.ts +0 -115
  82. package/dist/hooks/useActorMethod.d.ts.map +0 -1
  83. package/dist/hooks/useActorMethod.js +0 -250
  84. package/dist/hooks/useActorMethod.js.map +0 -1
  85. package/dist/hooks/useActorMutation.d.ts +0 -48
  86. package/dist/hooks/useActorMutation.d.ts.map +0 -1
  87. package/dist/hooks/useActorMutation.js +0 -69
  88. package/dist/hooks/useActorMutation.js.map +0 -1
  89. package/dist/hooks/useActorQuery.d.ts +0 -32
  90. package/dist/hooks/useActorQuery.d.ts.map +0 -1
  91. package/dist/hooks/useActorQuery.js +0 -46
  92. package/dist/hooks/useActorQuery.js.map +0 -1
  93. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +0 -39
  94. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +0 -1
  95. package/dist/hooks/useActorSuspenseInfiniteQuery.js +0 -56
  96. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +0 -1
  97. package/dist/hooks/useActorSuspenseQuery.d.ts +0 -32
  98. package/dist/hooks/useActorSuspenseQuery.d.ts.map +0 -1
  99. package/dist/hooks/useActorSuspenseQuery.js +0 -47
  100. package/dist/hooks/useActorSuspenseQuery.js.map +0 -1
  101. package/dist/types.d.ts +0 -270
  102. package/dist/types.d.ts.map +0 -1
  103. package/dist/types.js +0 -5
  104. package/dist/types.js.map +0 -1
  105. package/dist/utils.d.ts +0 -51
  106. package/dist/utils.d.ts.map +0 -1
  107. package/dist/utils.js +0 -105
  108. package/dist/utils.js.map +0 -1
  109. package/dist/validation.d.ts +0 -131
  110. package/dist/validation.d.ts.map +0 -1
  111. package/dist/validation.js +0 -125
  112. package/dist/validation.js.map +0 -1
  113. package/src/auth/authentication-manager.ts +0 -860
  114. package/src/auth/constants.ts +0 -32
  115. package/src/auth/createIdentityAttributeHooks.ts +0 -143
  116. package/src/auth/identity-attributes-manager.ts +0 -131
  117. package/src/auth/identity-attributes.ts +0 -270
  118. package/src/auth/index.ts +0 -7
  119. package/src/auth/local-ii-probe.ts +0 -147
  120. package/src/auth/types.ts +0 -200
  121. package/src/createActorHooks.ts +0 -220
  122. package/src/createInfiniteQuery.ts +0 -578
  123. package/src/createMutation.ts +0 -341
  124. package/src/createQuery.ts +0 -237
  125. package/src/createSuspenseInfiniteQuery.ts +0 -601
  126. package/src/createSuspenseQuery.ts +0 -234
  127. package/src/defineReactor.ts +0 -324
  128. package/src/hooks/createAuthHooks.ts +0 -189
  129. package/src/hooks/index.ts +0 -103
  130. package/src/hooks/useActorInfiniteQuery.ts +0 -205
  131. package/src/hooks/useActorMethod.ts +0 -507
  132. package/src/hooks/useActorMutation.ts +0 -205
  133. package/src/hooks/useActorQuery.ts +0 -124
  134. package/src/hooks/useActorSuspenseInfiniteQuery.ts +0 -205
  135. package/src/hooks/useActorSuspenseQuery.ts +0 -132
  136. package/src/index.ts +0 -23
  137. package/src/types.ts +0 -499
  138. package/src/utils.ts +0 -121
  139. package/src/validation.ts +0 -202
@@ -1,507 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useRef } from "react"
2
- import {
3
- useQuery,
4
- useMutation,
5
- type UseQueryResult,
6
- type UseMutationResult,
7
- type QueryKey,
8
- type QueryObserverOptions,
9
- } from "@tanstack/react-query"
10
- import {
11
- Reactor,
12
- BaseActor,
13
- FunctionName,
14
- TransformKey,
15
- ReactorArgs,
16
- ReactorReturnOk,
17
- ReactorQueryData,
18
- ReactorReturnErr,
19
- FunctionType,
20
- } from "@ic-reactor/core"
21
- import { CallConfig } from "@icp-sdk/core/agent"
22
- import { normalizeQueryData } from "../utils.js"
23
-
24
- /**
25
- * Configuration for useActorMethod hook.
26
- * Extends react-query's QueryObserverOptions with custom reactor params.
27
- *
28
- * This is a unified hook that handles both query and mutation methods.
29
- * Query-specific options (like refetchInterval) only apply to query methods.
30
- * Mutation-specific options (like invalidateQueries) only apply to mutation methods.
31
- */
32
- export interface UseActorMethodParameters<
33
- Service = BaseActor,
34
- Method extends FunctionName<Service> = FunctionName<Service>,
35
- Transform extends TransformKey = "candid",
36
- > extends Omit<
37
- QueryObserverOptions<
38
- ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
39
- ReactorReturnErr<Service, Method, Transform>,
40
- ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
41
- ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
42
- QueryKey
43
- >,
44
- "queryKey" | "queryFn"
45
- > {
46
- /** The reactor instance to use for method calls */
47
- reactor: Reactor<Service, Transform>
48
-
49
- /** The method name to call on the canister */
50
- functionName: Method
51
-
52
- /** Arguments to pass to the method (optional for parameterless methods) */
53
- args?: ReactorArgs<Service, Method, Transform>
54
-
55
- /** Agent call configuration (effectiveCanisterId, etc.) */
56
- callConfig?: CallConfig
57
-
58
- /** Custom query key (auto-generated if not provided) */
59
- queryKey?: QueryKey
60
-
61
- /**
62
- * Callback when the method call succeeds.
63
- * Works for both query and mutation methods.
64
- *
65
- * A method's `undefined` result arrives as `null`, as in `useActorQuery`,
66
- * for query and update methods alike.
67
- */
68
- onSuccess?: (
69
- data: ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>
70
- ) => void
71
-
72
- /**
73
- * Callback when the method call fails.
74
- * Works for both query and mutation methods.
75
- */
76
- onError?: (error: ReactorReturnErr<Service, Method, Transform>) => void
77
-
78
- /**
79
- * Query keys to invalidate after a successful mutation.
80
- * Only applies to mutation methods (updates).
81
- */
82
- invalidateQueries?: QueryKey[]
83
- }
84
-
85
- /**
86
- * Configuration type for bound useActorMethod hook (reactor omitted).
87
- * For use with createActorHooks.
88
- */
89
- export type UseActorMethodConfig<
90
- Service = BaseActor,
91
- Method extends FunctionName<Service> = FunctionName<Service>,
92
- Transform extends TransformKey = "candid",
93
- > = Omit<UseActorMethodParameters<Service, Method, Transform>, "reactor">
94
-
95
- /**
96
- * Result type for useActorMethod hook.
97
- * Provides a unified interface for both query and mutation methods.
98
- */
99
- export interface UseActorMethodResult<
100
- Service = BaseActor,
101
- Method extends FunctionName<Service> = FunctionName<Service>,
102
- Transform extends TransformKey = "candid",
103
- > {
104
- /**
105
- * The returned data from the method call. A method's `undefined` result is
106
- * `null` here, as in `useActorQuery`, so `undefined` only means no call has
107
- * settled yet.
108
- */
109
- data:
110
- ReactorQueryData<ReactorReturnOk<Service, Method, Transform>> | undefined
111
-
112
- /** Whether the method is currently executing */
113
- isLoading: boolean
114
-
115
- /** Alias for isLoading - whether a mutation is pending */
116
- isPending: boolean
117
-
118
- /** Whether there was an error */
119
- isError: boolean
120
-
121
- /** Whether the method has successfully completed at least once */
122
- isSuccess: boolean
123
-
124
- /** The error if one occurred */
125
- error: ReactorReturnErr<Service, Method, Transform> | null
126
-
127
- /** Whether this is a query method (true) or mutation method (false) */
128
- isQuery: boolean
129
-
130
- /** The function type (query, update, composite_query) */
131
- functionType: FunctionType
132
-
133
- /**
134
- * Call the method with optional arguments.
135
- * For queries: triggers a refetch
136
- * For mutations: executes the mutation with the provided args
137
- */
138
- call: (
139
- args?: ReactorArgs<Service, Method, Transform>
140
- ) => Promise<
141
- ReactorQueryData<ReactorReturnOk<Service, Method, Transform>> | undefined
142
- >
143
-
144
- /**
145
- * Reset the state (data and error).
146
- * For queries: resets this hook's own cache entry to its initial state, so
147
- * `data` goes back to `initialData` when one was given and is cleared
148
- * otherwise. An enabled hook then refetches. Other args of the same method
149
- * are left alone.
150
- * For mutations: resets the mutation state
151
- */
152
- reset: () => void
153
-
154
- /**
155
- * For queries only: Refetch the query
156
- */
157
- refetch: () => Promise<
158
- ReactorQueryData<ReactorReturnOk<Service, Method, Transform>> | undefined
159
- >
160
-
161
- // Expose underlying results for advanced use cases
162
- /** The raw query result (only available for query methods) */
163
- queryResult?: UseQueryResult<
164
- ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
165
- ReactorReturnErr<Service, Method, Transform>
166
- >
167
-
168
- /** The raw mutation result (only available for mutation methods) */
169
- mutationResult?: UseMutationResult<
170
- ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
171
- ReactorReturnErr<Service, Method, Transform>,
172
- ReactorArgs<Service, Method, Transform>
173
- >
174
- }
175
-
176
- /**
177
- * A unified hook for calling canister methods that automatically handles
178
- * both query and mutation methods based on the Candid interface.
179
- */
180
- export function useActorMethod<
181
- Service = BaseActor,
182
- Method extends FunctionName<Service> = FunctionName<Service>,
183
- Transform extends TransformKey = "candid",
184
- >({
185
- reactor,
186
- functionName,
187
- args,
188
- callConfig,
189
- queryKey: customQueryKey,
190
- enabled = true,
191
- onSuccess,
192
- onError,
193
- invalidateQueries,
194
- ...queryOptions
195
- }: UseActorMethodParameters<Service, Method, Transform>): UseActorMethodResult<
196
- Service,
197
- Method,
198
- Transform
199
- > {
200
- type TData = ReactorReturnOk<Service, Method, Transform>
201
- type TQueryData = ReactorQueryData<TData>
202
-
203
- // Determine if this is a query method by checking the IDL
204
- const isQuery = useMemo(() => {
205
- if (!reactor) throw new Error("Reactor instance is required")
206
- return reactor.isQueryMethod(functionName)
207
- }, [reactor, functionName])
208
-
209
- const functionType: FunctionType = isQuery ? "query" : "update"
210
-
211
- // Latest callbacks, read at dispatch time: this keeps a rerendered closure
212
- // from being ignored, and keeps callback identity out of the effect deps.
213
- const onSuccessRef = useRef(onSuccess)
214
- const onErrorRef = useRef(onError)
215
- onSuccessRef.current = onSuccess
216
- onErrorRef.current = onError
217
-
218
- // Build the key for a given set of call arguments.
219
- //
220
- // A custom `queryKey` is appended to the canister/function prefix rather than
221
- // replacing it, matching `useActorQuery`. Used verbatim it would carry no
222
- // canister id, so the entry would be invisible to the canister-scoped
223
- // invalidation `ClientManager.updateAgent` runs on identity change and would
224
- // keep serving the previous principal's data after sign-in or sign-out.
225
- const buildQueryKey = useCallback(
226
- (keyArgs: ReactorArgs<Service, Method, Transform> | undefined): QueryKey =>
227
- reactor.generateQueryKey(
228
- { functionName, args: keyArgs, queryKey: customQueryKey },
229
- callConfig
230
- ),
231
- // `canisterId` is mutable reactor state that `setCanisterId` can change,
232
- // while `reactor` itself stays the same object — so it has to be a
233
- // dependency in its own right or the key stays pinned to the old canister.
234
- [
235
- reactor,
236
- reactor.canisterId?.toString(),
237
- functionName,
238
- callConfig,
239
- customQueryKey,
240
- ]
241
- )
242
-
243
- const queryKey = useMemo(() => buildQueryKey(args), [buildQueryKey, args])
244
-
245
- // ============================================================================
246
- // Query Implementation
247
- // ============================================================================
248
-
249
- const queryResult = useQuery<
250
- TQueryData,
251
- ReactorReturnErr<Service, Method, Transform>
252
- >(
253
- {
254
- queryKey,
255
- // Callbacks deliberately do NOT live here: a queryFn runs once per fetch
256
- // attempt, so `onError` fired on every retry (four times with the default
257
- // QueryClient) and `onSuccess` never fired when data came from the cache
258
- // or a deduped sibling. They are dispatched from the settled observer
259
- // result below instead.
260
- //
261
- // TanStack Query fails a query whose queryFn resolves `undefined`, with
262
- // "data is undefined", and a successful call can resolve it. A
263
- // DisplayReactor decodes an `opt` None to `undefined`, and a `()` query
264
- // returns nothing. Every other query path normalizes the value to `null`.
265
- queryFn: async () =>
266
- normalizeQueryData<TData>(
267
- (await reactor.callMethod({
268
- functionName,
269
- args,
270
- callConfig,
271
- })) as TData
272
- ),
273
- enabled: isQuery && enabled,
274
- ...queryOptions,
275
- },
276
- reactor.queryClient
277
- )
278
-
279
- // Dispatch the query callbacks from the settled observer result, once per
280
- // distinct outcome. TanStack v5 removed `onSuccess`/`onError` from useQuery
281
- // for exactly this reason, so the settle timestamps are what identify a new
282
- // result: they also advance for a cache hit on a fresh mount, which is the
283
- // case that previously never notified at all.
284
- const notifiedSuccessAt = useRef<number | undefined>(undefined)
285
- const notifiedErrorAt = useRef<number | undefined>(undefined)
286
-
287
- const { status, data, error, dataUpdatedAt, errorUpdatedAt } = queryResult
288
-
289
- useEffect(() => {
290
- if (!isQuery) return
291
- if (status === "success" && dataUpdatedAt !== notifiedSuccessAt.current) {
292
- notifiedSuccessAt.current = dataUpdatedAt
293
- onSuccessRef.current?.(data)
294
- }
295
- }, [isQuery, status, data, dataUpdatedAt])
296
-
297
- useEffect(() => {
298
- if (!isQuery) return
299
- if (status === "error" && errorUpdatedAt !== notifiedErrorAt.current) {
300
- notifiedErrorAt.current = errorUpdatedAt
301
- onErrorRef.current?.(
302
- error as ReactorReturnErr<Service, Method, Transform>
303
- )
304
- }
305
- }, [isQuery, status, error, errorUpdatedAt])
306
-
307
- // ============================================================================
308
- // Mutation Implementation
309
- // ============================================================================
310
-
311
- const mutationResult = useMutation<
312
- TQueryData,
313
- ReactorReturnErr<Service, Method, Transform>,
314
- ReactorArgs<Service, Method, Transform>
315
- >(
316
- {
317
- mutationKey: queryKey,
318
- // Normalized like the query branch, so `data`, `call()` and `onSuccess`
319
- // mean the same thing for both kinds of method. The hook cannot type the
320
- // two branches apart: a Candid service type does not say which methods
321
- // are queries.
322
- mutationFn: async (mutationArgs) =>
323
- normalizeQueryData<TData>(
324
- (await reactor.callMethod({
325
- functionName,
326
- args: mutationArgs ?? args,
327
- callConfig,
328
- })) as TData
329
- ),
330
- onSuccess: (data) => {
331
- onSuccessRef.current?.(data)
332
- // Invalidate specified queries after successful mutation
333
- if (invalidateQueries && invalidateQueries.length > 0) {
334
- invalidateQueries.forEach((key) => {
335
- void reactor.queryClient.invalidateQueries({ queryKey: key })
336
- })
337
- }
338
- },
339
- onError: (error) => {
340
- onErrorRef.current?.(error)
341
- },
342
- },
343
- reactor.queryClient
344
- )
345
-
346
- // ============================================================================
347
- // Unified Call Function
348
- // ============================================================================
349
-
350
- const call = useCallback(
351
- async (
352
- callArgs?: ReactorArgs<Service, Method, Transform>
353
- ): Promise<TQueryData | undefined> => {
354
- if (isQuery) {
355
- // For queries, refetch with new args if provided
356
- if (callArgs !== undefined) {
357
- // Key on the args actually being called. Reusing the hook's
358
- // mount-time key would store this result under the previous args'
359
- // entry — poisoning it for every other reader — and let fetchQuery
360
- // dedupe onto an in-flight request for the old args, returning that
361
- // response as though it answered this one.
362
- try {
363
- const result = await reactor.queryClient.fetchQuery<TQueryData>({
364
- queryKey: buildQueryKey(callArgs),
365
- // Normalize for the same reason as the observer's queryFn.
366
- queryFn: async () =>
367
- normalizeQueryData<TData>(
368
- (await reactor.callMethod({
369
- functionName,
370
- args: callArgs,
371
- callConfig,
372
- })) as TData
373
- ),
374
- staleTime: 0,
375
- })
376
- // Dispatched here rather than by the observer effect: this result
377
- // lands under the called args' key, which the mounted observer (bound
378
- // to the hook's own args) does not watch. That separation is also why
379
- // this can no longer double-fire the way it did when both wrote to
380
- // the same key.
381
- onSuccessRef.current?.(result)
382
- return result
383
- } catch (error) {
384
- onErrorRef.current?.(
385
- error as ReactorReturnErr<Service, Method, Transform>
386
- )
387
- return undefined
388
- }
389
- }
390
- // Otherwise just refetch
391
- const { data } = await queryResult.refetch()
392
- return data
393
- } else {
394
- // For mutations, execute with provided args
395
- return mutationResult
396
- .mutateAsync(callArgs as ReactorArgs<Service, Method, Transform>)
397
- .catch(() => undefined)
398
- }
399
- },
400
- [
401
- isQuery,
402
- reactor,
403
- functionName,
404
- callConfig,
405
- buildQueryKey,
406
- queryResult,
407
- mutationResult,
408
- ]
409
- )
410
-
411
- // ============================================================================
412
- // Reset Function
413
- // ============================================================================
414
-
415
- const reset = useCallback(() => {
416
- if (isQuery) {
417
- // Reset, not remove. `removeQueries` drops the entry without notifying
418
- // its observers, so this hook kept rendering the old data while bound to
419
- // a query that was no longer in the cache. No later invalidation reached
420
- // it, including the one an identity switch runs. `resetQueries` notifies
421
- // the observer and refetches the entry when the hook is enabled. It is
422
- // `exact` because only this hook's entry should reset. A prefix match on
423
- // a no-args key would also reset every args variant other hooks render.
424
- void reactor.queryClient.resetQueries({ queryKey, exact: true })
425
- } else {
426
- mutationResult.reset()
427
- }
428
- }, [isQuery, reactor, queryKey, mutationResult])
429
-
430
- // ============================================================================
431
- // Refetch Function
432
- // ============================================================================
433
-
434
- const refetch = useCallback(async () => {
435
- if (isQuery) {
436
- const result = await queryResult.refetch()
437
- return result.data
438
- }
439
- return undefined
440
- }, [isQuery, queryResult])
441
-
442
- // ============================================================================
443
- // Return Unified Result
444
- // ============================================================================
445
-
446
- if (isQuery) {
447
- return {
448
- data: queryResult.data,
449
- isLoading: queryResult.isLoading,
450
- isPending: queryResult.isLoading,
451
- isError: queryResult.isError,
452
- isSuccess: queryResult.isSuccess,
453
- error: queryResult.error,
454
- isQuery: true,
455
- functionType,
456
- call,
457
- reset,
458
- refetch,
459
- queryResult,
460
- } as UseActorMethodResult<Service, Method, Transform>
461
- } else {
462
- return {
463
- data: mutationResult.data,
464
- isLoading: mutationResult.isPending,
465
- isPending: mutationResult.isPending,
466
- isError: mutationResult.isError,
467
- isSuccess: mutationResult.isSuccess,
468
- error: mutationResult.error,
469
- isQuery: false,
470
- functionType,
471
- call,
472
- reset,
473
- refetch,
474
- mutationResult,
475
- } as UseActorMethodResult<Service, Method, Transform>
476
- }
477
- }
478
-
479
- /**
480
- * Creates a bound useMethod hook for a specific reactor instance.
481
- *
482
- * @example
483
- * ```tsx
484
- * const { useMethod } = createActorMethodHooks(reactor)
485
- * ```
486
- */
487
- export function createActorMethodHooks<
488
- Service = BaseActor,
489
- Transform extends TransformKey = "candid",
490
- >(reactor: Reactor<Service, Transform>) {
491
- return {
492
- /**
493
- * Hook for calling methods on the bound reactor.
494
- */
495
- useMethod: <Method extends FunctionName<Service>>(
496
- config: Omit<
497
- UseActorMethodParameters<Service, Method, Transform>,
498
- "reactor"
499
- >
500
- ) =>
501
- useActorMethod({ ...config, reactor } as UseActorMethodParameters<
502
- Service,
503
- Method,
504
- Transform
505
- >),
506
- }
507
- }
@@ -1,205 +0,0 @@
1
- import { useCallback } from "react"
2
- import {
3
- useMutation,
4
- UseMutationOptions,
5
- UseMutationResult,
6
- QueryKey,
7
- } from "@tanstack/react-query"
8
- import {
9
- Reactor,
10
- ReactorArgs,
11
- ReactorReturnOk,
12
- FunctionName,
13
- TransformKey,
14
- ReactorReturnErr,
15
- isCanisterError,
16
- CanisterError,
17
- ErrResult,
18
- ActorMethodReturnType,
19
- TransformReturnRegistry,
20
- } from "@ic-reactor/core"
21
- import { CallConfig } from "@icp-sdk/core/agent"
22
-
23
- export interface UseActorMutationParameters<
24
- Service,
25
- Method extends FunctionName<Service>,
26
- Transform extends TransformKey = "candid",
27
- TOnMutateResult = unknown,
28
- > extends Omit<
29
- UseMutationOptions<
30
- ReactorReturnOk<Service, Method, Transform>,
31
- ReactorReturnErr<Service, Method, Transform>,
32
- ReactorArgs<Service, Method, Transform>,
33
- TOnMutateResult
34
- >,
35
- "mutationFn"
36
- > {
37
- reactor: Reactor<Service, Transform>
38
- functionName: Method
39
- callConfig?: CallConfig
40
- /**
41
- * Queries to invalidate upon successful mutation.
42
- *
43
- * `undefined` entries are skipped, so the common
44
- * `[maybeQuery?.getQueryKey()]` idiom is safe when the optional query object
45
- * is absent.
46
- */
47
- invalidateQueries?: (QueryKey | undefined)[]
48
- /**
49
- * Callback for canister-level business logic errors.
50
- * Called when the canister returns a Result { Err: E } variant.
51
- * Separate from `onError`, which fires for all errors including network failures.
52
- */
53
- onCanisterError?: (
54
- error: CanisterError<
55
- TransformReturnRegistry<
56
- ErrResult<ActorMethodReturnType<Service[Method]>>,
57
- Service
58
- >[Transform]
59
- >,
60
- variables: ReactorArgs<Service, Method, Transform>
61
- ) => void
62
- }
63
-
64
- export type UseActorMutationConfig<
65
- Service,
66
- Method extends FunctionName<Service>,
67
- Transform extends TransformKey = "candid",
68
- TOnMutateResult = unknown,
69
- > = Omit<
70
- UseActorMutationParameters<Service, Method, Transform, TOnMutateResult>,
71
- "reactor"
72
- >
73
-
74
- export type UseActorMutationResult<
75
- Service,
76
- Method extends FunctionName<Service>,
77
- Transform extends TransformKey = "candid",
78
- TOnMutateResult = unknown,
79
- > = UseMutationResult<
80
- ReactorReturnOk<Service, Method, Transform>,
81
- ReactorReturnErr<Service, Method, Transform>,
82
- ReactorArgs<Service, Method, Transform>,
83
- TOnMutateResult
84
- >
85
-
86
- /**
87
- * Hook for executing mutation calls on a canister.
88
- * Use this for component-level mutation flows.
89
- * For non-React usage, prefer `createMutation(...).execute(args)`.
90
- *
91
- * @example
92
- * const { mutate, isPending } = useActorMutation({
93
- * reactor,
94
- * functionName: "transfer",
95
- * onSuccess: () => console.log("Success!"),
96
- * onCanisterError: (err) => console.error("Canister Err:", err.code),
97
- * })
98
- *
99
- * @example
100
- * const transferMutation = createMutation(reactor, {
101
- * functionName: "transfer",
102
- * onCanisterError: (err) => console.error(err.code),
103
- * })
104
- *
105
- * // Non-React execution path
106
- * await transferMutation.execute([{ to: "aaaaa-aa", amount: "100" }])
107
- */
108
- export const useActorMutation = <
109
- Service,
110
- Method extends FunctionName<Service>,
111
- Transform extends TransformKey = "candid",
112
- TOnMutateResult = unknown,
113
- >({
114
- reactor,
115
- functionName,
116
- invalidateQueries,
117
- onSuccess,
118
- onError,
119
- onCanisterError,
120
- callConfig,
121
- ...options
122
- }: UseActorMutationParameters<
123
- Service,
124
- Method,
125
- Transform,
126
- TOnMutateResult
127
- >): UseActorMutationResult<Service, Method, Transform, TOnMutateResult> => {
128
- const mutationFn = useCallback(
129
- async (args: ReactorArgs<Service, Method, Transform>) =>
130
- reactor.callMethod({ functionName, callConfig, args }),
131
- [reactor, functionName, callConfig]
132
- )
133
-
134
- const handleSuccess = useCallback(
135
- async (
136
- ...params: Parameters<
137
- NonNullable<
138
- UseMutationOptions<
139
- ReactorReturnOk<Service, Method, Transform>,
140
- ReactorReturnErr<Service, Method, Transform>,
141
- ReactorArgs<Service, Method, Transform>,
142
- TOnMutateResult
143
- >["onSuccess"]
144
- >
145
- >
146
- ) => {
147
- if (invalidateQueries) {
148
- // Skip undefined entries. React Query reads `{ queryKey: undefined }`
149
- // as "match everything", so a single undefined — which the natural
150
- // `invalidateQueries: [maybeQuery?.getQueryKey()]` idiom produces
151
- // whenever the optional query object is absent — would invalidate every
152
- // query in the client, including an app's unrelated non-canister ones.
153
- // `createMutation.invalidateAll` already filters the same way.
154
- await Promise.all(
155
- invalidateQueries
156
- .filter((queryKey) => queryKey !== undefined)
157
- .map((queryKey) =>
158
- reactor.queryClient.invalidateQueries({ queryKey })
159
- )
160
- )
161
- }
162
- await onSuccess?.(...params)
163
- },
164
- [reactor, invalidateQueries, onSuccess]
165
- )
166
-
167
- const handleError = useCallback(
168
- async (
169
- error: ReactorReturnErr<Service, Method, Transform>,
170
- variables: ReactorArgs<Service, Method, Transform>,
171
- context: unknown,
172
- mutation: unknown
173
- ) => {
174
- if (isCanisterError(error)) {
175
- onCanisterError?.(error as any, variables)
176
- }
177
- // Awaited like `onSuccess` above. TanStack Query waits for the promise
178
- // `onError` returns before it runs `onSettled` and settles the mutation,
179
- // and it can only wait for a promise this wrapper passes back.
180
- await onError?.(error, variables, context as any, mutation as any)
181
- },
182
- [onCanisterError, onError]
183
- )
184
-
185
- // Not memoized. The deps could only ever list the values destructured above,
186
- // never the `options` rest bucket, so everything passed straight through —
187
- // `onMutate`, `onSettled`, `retry`, `meta`, `gcTime` — was frozen at the first
188
- // render: `useMutation` calls `observer.setOptions` from an effect keyed on
189
- // the options identity, so a later render's closures never reached it. A
190
- // component passing only `onSettled` would report the recipient selected at
191
- // mount rather than at submit.
192
- //
193
- // `createMutation`'s own hook builds its options inline for the same reason.
194
- // A fresh object per render costs nothing here: `setOptions` diffs the values
195
- // rather than the reference.
196
- return useMutation(
197
- {
198
- ...options,
199
- mutationFn,
200
- onSuccess: handleSuccess,
201
- onError: handleError,
202
- },
203
- reactor.queryClient
204
- )
205
- }