@ic-reactor/react 3.12.3 → 3.12.5

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 (42) hide show
  1. package/dist/auth/authentication-manager.d.ts +1 -1
  2. package/dist/auth/authentication-manager.js +1 -1
  3. package/dist/createActorHooks.d.ts +18 -7
  4. package/dist/createActorHooks.d.ts.map +1 -1
  5. package/dist/createActorHooks.js.map +1 -1
  6. package/dist/createMutation.d.ts +1 -1
  7. package/dist/createMutation.d.ts.map +1 -1
  8. package/dist/createMutation.js +73 -17
  9. package/dist/createMutation.js.map +1 -1
  10. package/dist/defineReactor.js +1 -4
  11. package/dist/defineReactor.js.map +1 -1
  12. package/dist/hooks/createAuthHooks.d.ts +7 -2
  13. package/dist/hooks/createAuthHooks.d.ts.map +1 -1
  14. package/dist/hooks/createAuthHooks.js +22 -8
  15. package/dist/hooks/createAuthHooks.js.map +1 -1
  16. package/dist/hooks/useActorInfiniteQuery.d.ts +3 -3
  17. package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
  18. package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
  19. package/dist/hooks/useActorMethod.d.ts +21 -11
  20. package/dist/hooks/useActorMethod.d.ts.map +1 -1
  21. package/dist/hooks/useActorMethod.js +29 -14
  22. package/dist/hooks/useActorMethod.js.map +1 -1
  23. package/dist/hooks/useActorMutation.d.ts +5 -5
  24. package/dist/hooks/useActorMutation.d.ts.map +1 -1
  25. package/dist/hooks/useActorMutation.js +5 -2
  26. package/dist/hooks/useActorMutation.js.map +1 -1
  27. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +3 -3
  28. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
  29. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
  30. package/dist/types.d.ts +12 -6
  31. package/dist/types.d.ts.map +1 -1
  32. package/package.json +7 -8
  33. package/src/auth/authentication-manager.ts +1 -1
  34. package/src/createActorHooks.ts +60 -23
  35. package/src/createMutation.ts +153 -30
  36. package/src/defineReactor.ts +1 -2
  37. package/src/hooks/createAuthHooks.ts +34 -11
  38. package/src/hooks/useActorInfiniteQuery.ts +34 -6
  39. package/src/hooks/useActorMethod.ts +79 -38
  40. package/src/hooks/useActorMutation.ts +24 -9
  41. package/src/hooks/useActorSuspenseInfiniteQuery.ts +19 -6
  42. package/src/types.ts +29 -8
@@ -14,10 +14,12 @@ import {
14
14
  TransformKey,
15
15
  ReactorArgs,
16
16
  ReactorReturnOk,
17
+ ReactorQueryData,
17
18
  ReactorReturnErr,
18
19
  FunctionType,
19
20
  } from "@ic-reactor/core"
20
21
  import { CallConfig } from "@icp-sdk/core/agent"
22
+ import { normalizeQueryData } from "../utils.js"
21
23
 
22
24
  /**
23
25
  * Configuration for useActorMethod hook.
@@ -33,10 +35,10 @@ export interface UseActorMethodParameters<
33
35
  Transform extends TransformKey = "candid",
34
36
  > extends Omit<
35
37
  QueryObserverOptions<
36
- ReactorReturnOk<Service, Method, Transform>,
38
+ ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
37
39
  ReactorReturnErr<Service, Method, Transform>,
38
- ReactorReturnOk<Service, Method, Transform>,
39
- ReactorReturnOk<Service, Method, Transform>,
40
+ ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
41
+ ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
40
42
  QueryKey
41
43
  >,
42
44
  "queryKey" | "queryFn"
@@ -59,8 +61,13 @@ export interface UseActorMethodParameters<
59
61
  /**
60
62
  * Callback when the method call succeeds.
61
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.
62
67
  */
63
- onSuccess?: (data: ReactorReturnOk<Service, Method, Transform>) => void
68
+ onSuccess?: (
69
+ data: ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>
70
+ ) => void
64
71
 
65
72
  /**
66
73
  * Callback when the method call fails.
@@ -94,8 +101,13 @@ export interface UseActorMethodResult<
94
101
  Method extends FunctionName<Service> = FunctionName<Service>,
95
102
  Transform extends TransformKey = "candid",
96
103
  > {
97
- /** The returned data from the method call */
98
- data: ReactorReturnOk<Service, Method, Transform> | undefined
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
99
111
 
100
112
  /** Whether the method is currently executing */
101
113
  isLoading: boolean
@@ -125,11 +137,16 @@ export interface UseActorMethodResult<
125
137
  */
126
138
  call: (
127
139
  args?: ReactorArgs<Service, Method, Transform>
128
- ) => Promise<ReactorReturnOk<Service, Method, Transform> | undefined>
140
+ ) => Promise<
141
+ ReactorQueryData<ReactorReturnOk<Service, Method, Transform>> | undefined
142
+ >
129
143
 
130
144
  /**
131
- * Reset the state (clear data and error).
132
- * For queries: removes the query from cache
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.
133
150
  * For mutations: resets the mutation state
134
151
  */
135
152
  reset: () => void
@@ -138,19 +155,19 @@ export interface UseActorMethodResult<
138
155
  * For queries only: Refetch the query
139
156
  */
140
157
  refetch: () => Promise<
141
- ReactorReturnOk<Service, Method, Transform> | undefined
158
+ ReactorQueryData<ReactorReturnOk<Service, Method, Transform>> | undefined
142
159
  >
143
160
 
144
161
  // Expose underlying results for advanced use cases
145
162
  /** The raw query result (only available for query methods) */
146
163
  queryResult?: UseQueryResult<
147
- ReactorReturnOk<Service, Method, Transform>,
164
+ ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
148
165
  ReactorReturnErr<Service, Method, Transform>
149
166
  >
150
167
 
151
168
  /** The raw mutation result (only available for mutation methods) */
152
169
  mutationResult?: UseMutationResult<
153
- ReactorReturnOk<Service, Method, Transform>,
170
+ ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
154
171
  ReactorReturnErr<Service, Method, Transform>,
155
172
  ReactorArgs<Service, Method, Transform>
156
173
  >
@@ -180,6 +197,9 @@ export function useActorMethod<
180
197
  Method,
181
198
  Transform
182
199
  > {
200
+ type TData = ReactorReturnOk<Service, Method, Transform>
201
+ type TQueryData = ReactorQueryData<TData>
202
+
183
203
  // Determine if this is a query method by checking the IDL
184
204
  const isQuery = useMemo(() => {
185
205
  if (!reactor) throw new Error("Reactor instance is required")
@@ -227,7 +247,7 @@ export function useActorMethod<
227
247
  // ============================================================================
228
248
 
229
249
  const queryResult = useQuery<
230
- ReactorReturnOk<Service, Method, Transform>,
250
+ TQueryData,
231
251
  ReactorReturnErr<Service, Method, Transform>
232
252
  >(
233
253
  {
@@ -237,12 +257,19 @@ export function useActorMethod<
237
257
  // QueryClient) and `onSuccess` never fired when data came from the cache
238
258
  // or a deduped sibling. They are dispatched from the settled observer
239
259
  // result below instead.
240
- queryFn: () =>
241
- reactor.callMethod({
242
- functionName,
243
- args,
244
- callConfig,
245
- }),
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
+ ),
246
273
  enabled: isQuery && enabled,
247
274
  ...queryOptions,
248
275
  },
@@ -282,26 +309,30 @@ export function useActorMethod<
282
309
  // ============================================================================
283
310
 
284
311
  const mutationResult = useMutation<
285
- ReactorReturnOk<Service, Method, Transform>,
312
+ TQueryData,
286
313
  ReactorReturnErr<Service, Method, Transform>,
287
314
  ReactorArgs<Service, Method, Transform>
288
315
  >(
289
316
  {
290
317
  mutationKey: queryKey,
291
- mutationFn: async (mutationArgs) => {
292
- const result = await reactor.callMethod({
293
- functionName,
294
- args: mutationArgs ?? args,
295
- callConfig,
296
- })
297
- return result
298
- },
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
+ ),
299
330
  onSuccess: (data) => {
300
331
  onSuccessRef.current?.(data)
301
332
  // Invalidate specified queries after successful mutation
302
333
  if (invalidateQueries && invalidateQueries.length > 0) {
303
334
  invalidateQueries.forEach((key) => {
304
- reactor.queryClient.invalidateQueries({ queryKey: key })
335
+ void reactor.queryClient.invalidateQueries({ queryKey: key })
305
336
  })
306
337
  }
307
338
  },
@@ -319,7 +350,7 @@ export function useActorMethod<
319
350
  const call = useCallback(
320
351
  async (
321
352
  callArgs?: ReactorArgs<Service, Method, Transform>
322
- ): Promise<ReactorReturnOk<Service, Method, Transform> | undefined> => {
353
+ ): Promise<TQueryData | undefined> => {
323
354
  if (isQuery) {
324
355
  // For queries, refetch with new args if provided
325
356
  if (callArgs !== undefined) {
@@ -329,14 +360,17 @@ export function useActorMethod<
329
360
  // dedupe onto an in-flight request for the old args, returning that
330
361
  // response as though it answered this one.
331
362
  try {
332
- const result = await reactor.queryClient.fetchQuery({
363
+ const result = await reactor.queryClient.fetchQuery<TQueryData>({
333
364
  queryKey: buildQueryKey(callArgs),
334
- queryFn: () =>
335
- reactor.callMethod({
336
- functionName,
337
- args: callArgs,
338
- callConfig,
339
- }),
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
+ ),
340
374
  staleTime: 0,
341
375
  })
342
376
  // Dispatched here rather than by the observer effect: this result
@@ -380,7 +414,14 @@ export function useActorMethod<
380
414
 
381
415
  const reset = useCallback(() => {
382
416
  if (isQuery) {
383
- reactor.queryClient.removeQueries({ queryKey })
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 })
384
425
  } else {
385
426
  mutationResult.reset()
386
427
  }
@@ -24,11 +24,13 @@ export interface UseActorMutationParameters<
24
24
  Service,
25
25
  Method extends FunctionName<Service>,
26
26
  Transform extends TransformKey = "candid",
27
+ TOnMutateResult = unknown,
27
28
  > extends Omit<
28
29
  UseMutationOptions<
29
30
  ReactorReturnOk<Service, Method, Transform>,
30
31
  ReactorReturnErr<Service, Method, Transform>,
31
- ReactorArgs<Service, Method, Transform>
32
+ ReactorArgs<Service, Method, Transform>,
33
+ TOnMutateResult
32
34
  >,
33
35
  "mutationFn"
34
36
  > {
@@ -51,7 +53,8 @@ export interface UseActorMutationParameters<
51
53
  onCanisterError?: (
52
54
  error: CanisterError<
53
55
  TransformReturnRegistry<
54
- ErrResult<ActorMethodReturnType<Service[Method]>>
56
+ ErrResult<ActorMethodReturnType<Service[Method]>>,
57
+ Service
55
58
  >[Transform]
56
59
  >,
57
60
  variables: ReactorArgs<Service, Method, Transform>
@@ -62,16 +65,22 @@ export type UseActorMutationConfig<
62
65
  Service,
63
66
  Method extends FunctionName<Service>,
64
67
  Transform extends TransformKey = "candid",
65
- > = Omit<UseActorMutationParameters<Service, Method, Transform>, "reactor">
68
+ TOnMutateResult = unknown,
69
+ > = Omit<
70
+ UseActorMutationParameters<Service, Method, Transform, TOnMutateResult>,
71
+ "reactor"
72
+ >
66
73
 
67
74
  export type UseActorMutationResult<
68
75
  Service,
69
76
  Method extends FunctionName<Service>,
70
77
  Transform extends TransformKey = "candid",
78
+ TOnMutateResult = unknown,
71
79
  > = UseMutationResult<
72
80
  ReactorReturnOk<Service, Method, Transform>,
73
81
  ReactorReturnErr<Service, Method, Transform>,
74
- ReactorArgs<Service, Method, Transform>
82
+ ReactorArgs<Service, Method, Transform>,
83
+ TOnMutateResult
75
84
  >
76
85
 
77
86
  /**
@@ -100,6 +109,7 @@ export const useActorMutation = <
100
109
  Service,
101
110
  Method extends FunctionName<Service>,
102
111
  Transform extends TransformKey = "candid",
112
+ TOnMutateResult = unknown,
103
113
  >({
104
114
  reactor,
105
115
  functionName,
@@ -112,8 +122,9 @@ export const useActorMutation = <
112
122
  }: UseActorMutationParameters<
113
123
  Service,
114
124
  Method,
115
- Transform
116
- >): UseActorMutationResult<Service, Method, Transform> => {
125
+ Transform,
126
+ TOnMutateResult
127
+ >): UseActorMutationResult<Service, Method, Transform, TOnMutateResult> => {
117
128
  const mutationFn = useCallback(
118
129
  async (args: ReactorArgs<Service, Method, Transform>) =>
119
130
  reactor.callMethod({ functionName, callConfig, args }),
@@ -127,7 +138,8 @@ export const useActorMutation = <
127
138
  UseMutationOptions<
128
139
  ReactorReturnOk<Service, Method, Transform>,
129
140
  ReactorReturnErr<Service, Method, Transform>,
130
- ReactorArgs<Service, Method, Transform>
141
+ ReactorArgs<Service, Method, Transform>,
142
+ TOnMutateResult
131
143
  >["onSuccess"]
132
144
  >
133
145
  >
@@ -153,7 +165,7 @@ export const useActorMutation = <
153
165
  )
154
166
 
155
167
  const handleError = useCallback(
156
- (
168
+ async (
157
169
  error: ReactorReturnErr<Service, Method, Transform>,
158
170
  variables: ReactorArgs<Service, Method, Transform>,
159
171
  context: unknown,
@@ -162,7 +174,10 @@ export const useActorMutation = <
162
174
  if (isCanisterError(error)) {
163
175
  onCanisterError?.(error as any, variables)
164
176
  }
165
- onError?.(error, variables, context as any, mutation as any)
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)
166
181
  },
167
182
  [onCanisterError, onError]
168
183
  )
@@ -67,12 +67,17 @@ export type UseActorSuspenseInfiniteQueryConfig<
67
67
  Method extends FunctionName<Service>,
68
68
  Transform extends TransformKey = "candid",
69
69
  TPageParam = unknown,
70
+ Selected = InfiniteData<
71
+ ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
72
+ TPageParam
73
+ >,
70
74
  > = Omit<
71
75
  UseActorSuspenseInfiniteQueryParameters<
72
76
  Service,
73
77
  Method,
74
78
  Transform,
75
- TPageParam
79
+ TPageParam,
80
+ Selected
76
81
  >,
77
82
  "reactor"
78
83
  >
@@ -82,11 +87,12 @@ export type UseActorSuspenseInfiniteQueryResult<
82
87
  Method extends FunctionName<Service>,
83
88
  Transform extends TransformKey = "candid",
84
89
  TPageParam = unknown,
85
- > = UseSuspenseInfiniteQueryResult<
86
- InfiniteData<
90
+ Selected = InfiniteData<
87
91
  ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
88
92
  TPageParam
89
93
  >,
94
+ > = UseSuspenseInfiniteQueryResult<
95
+ Selected,
90
96
  ReactorReturnErr<Service, Method, Transform>
91
97
  >
92
98
 
@@ -107,6 +113,10 @@ export const useActorSuspenseInfiniteQuery = <
107
113
  Method extends FunctionName<Service>,
108
114
  Transform extends TransformKey = "candid",
109
115
  TPageParam = unknown,
116
+ Selected = InfiniteData<
117
+ ReactorQueryData<ReactorReturnOk<Service, Method, Transform>>,
118
+ TPageParam
119
+ >,
110
120
  >({
111
121
  reactor,
112
122
  functionName,
@@ -118,12 +128,14 @@ export const useActorSuspenseInfiniteQuery = <
118
128
  Service,
119
129
  Method,
120
130
  Transform,
121
- TPageParam
131
+ TPageParam,
132
+ Selected
122
133
  >): UseActorSuspenseInfiniteQueryResult<
123
134
  Service,
124
135
  Method,
125
136
  Transform,
126
- TPageParam
137
+ TPageParam,
138
+ Selected
127
139
  > => {
128
140
  // Always pass queryKey through generateQueryKey so it is merged with the
129
141
  // reactor/function identity. Using the custom key verbatim would cause cache
@@ -187,6 +199,7 @@ export const useActorSuspenseInfiniteQuery = <
187
199
  Service,
188
200
  Method,
189
201
  Transform,
190
- TPageParam
202
+ TPageParam,
203
+ Selected
191
204
  >
192
205
  }
package/src/types.ts CHANGED
@@ -339,16 +339,21 @@ export interface SuspenseQueryResult<
339
339
 
340
340
  /**
341
341
  * Configuration for createMutation and useActorMutation.
342
+ *
343
+ * @template TOnMutateResult - The value `onMutate` returns, which `onSuccess`,
344
+ * `onError` and `onSettled` receive. TypeScript infers it from `onMutate`.
342
345
  */
343
346
  export interface MutationConfig<
344
347
  Service = BaseActor,
345
348
  Method extends FunctionName<Service> = FunctionName<Service>,
346
349
  Transform extends TransformKey = "candid",
350
+ TOnMutateResult = unknown,
347
351
  > extends Omit<
348
352
  UseMutationOptions<
349
353
  ReactorReturnOk<Service, Method, Transform>,
350
354
  ReactorReturnErr<Service, Method, Transform>,
351
- ReactorArgs<Service, Method, Transform>
355
+ ReactorArgs<Service, Method, Transform>,
356
+ TOnMutateResult
352
357
  >,
353
358
  "mutationFn"
354
359
  > {
@@ -387,7 +392,12 @@ export interface MutationConfig<
387
392
  * ```
388
393
  */
389
394
  onCanisterError?: (
390
- error: CanisterError<unknown>,
395
+ error: CanisterError<
396
+ TransformReturnRegistry<
397
+ ErrResult<ActorMethodReturnType<Service[Method]>>,
398
+ Service
399
+ >[Transform]
400
+ >,
391
401
  variables: ReactorArgs<Service, Method, Transform>
392
402
  ) => void
393
403
  }
@@ -399,21 +409,30 @@ export type MutationFactoryConfig<
399
409
  Service = BaseActor,
400
410
  Method extends FunctionName<Service> = FunctionName<Service>,
401
411
  Transform extends TransformKey = "candid",
402
- > = Omit<MutationConfig<Service, Method, Transform>, "onSuccess">
412
+ TOnMutateResult = unknown,
413
+ > = Omit<
414
+ MutationConfig<Service, Method, Transform, TOnMutateResult>,
415
+ "onSuccess"
416
+ >
403
417
 
404
418
  /**
405
419
  * Options for useMutation hook.
406
420
  * Extends React Query's UseMutationOptions with invalidateQueries support.
421
+ *
422
+ * @template TOnMutateResult - The value `onMutate` returns, which `onSuccess`,
423
+ * `onError` and `onSettled` receive. TypeScript infers it from `onMutate`.
407
424
  */
408
425
  export interface MutationHookOptions<
409
426
  Service = BaseActor,
410
427
  Method extends FunctionName<Service> = FunctionName<Service>,
411
428
  Transform extends TransformKey = "candid",
429
+ TOnMutateResult = unknown,
412
430
  > extends Omit<
413
431
  UseMutationOptions<
414
432
  ReactorReturnOk<Service, Method, Transform>,
415
433
  ReactorReturnErr<Service, Method, Transform>,
416
- ReactorArgs<Service, Method, Transform>
434
+ ReactorArgs<Service, Method, Transform>,
435
+ TOnMutateResult
417
436
  >,
418
437
  "mutationFn"
419
438
  > {
@@ -438,7 +457,8 @@ export interface MutationHookOptions<
438
457
  onCanisterError?: (
439
458
  error: CanisterError<
440
459
  TransformReturnRegistry<
441
- ErrResult<ActorMethodReturnType<Service[Method]>>
460
+ ErrResult<ActorMethodReturnType<Service[Method]>>,
461
+ Service
442
462
  >[Transform]
443
463
  >,
444
464
  variables: ReactorArgs<Service, Method, Transform>
@@ -463,12 +483,13 @@ export interface MutationResult<
463
483
  * invalidateQueries: [userBalanceQuery], // Auto-invalidate after success!
464
484
  * })
465
485
  */
466
- useMutation: (
467
- options?: MutationHookOptions<Service, Method, Transform>
486
+ useMutation: <TOnMutateResult = unknown>(
487
+ options?: MutationHookOptions<Service, Method, Transform, TOnMutateResult>
468
488
  ) => UseMutationResult<
469
489
  ReactorReturnOk<Service, Method, Transform>,
470
490
  ReactorReturnErr<Service, Method, Transform>,
471
- ReactorArgs<Service, Method, Transform>
491
+ ReactorArgs<Service, Method, Transform>,
492
+ TOnMutateResult
472
493
  >
473
494
 
474
495
  /** Execute the update call directly (outside of React) */