@ic-reactor/react 3.2.0 → 3.3.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.
- package/README.md +58 -4
- package/dist/createActorHooks.d.ts +1 -1
- package/dist/createActorHooks.d.ts.map +1 -1
- package/dist/createActorHooks.js +10 -25
- package/dist/createActorHooks.js.map +1 -1
- package/dist/createInfiniteQuery.d.ts.map +1 -1
- package/dist/createInfiniteQuery.js +2 -12
- package/dist/createInfiniteQuery.js.map +1 -1
- package/dist/createMutation.d.ts.map +1 -1
- package/dist/createMutation.js +39 -56
- package/dist/createMutation.js.map +1 -1
- package/dist/createQuery.d.ts.map +1 -1
- package/dist/createQuery.js +31 -39
- package/dist/createQuery.js.map +1 -1
- package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/createSuspenseInfiniteQuery.js +1 -14
- package/dist/createSuspenseInfiniteQuery.js.map +1 -1
- package/dist/createSuspenseQuery.d.ts.map +1 -1
- package/dist/createSuspenseQuery.js +30 -39
- package/dist/createSuspenseQuery.js.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.js +4 -5
- package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
- package/dist/hooks/useActorMutation.d.ts +9 -2
- package/dist/hooks/useActorMutation.d.ts.map +1 -1
- package/dist/hooks/useActorMutation.js +15 -15
- package/dist/hooks/useActorMutation.js.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js +4 -5
- package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +26 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +41 -0
- package/dist/utils.js.map +1 -0
- package/package.json +9 -9
- package/src/createActorHooks.ts +32 -31
- package/src/createInfiniteQuery.ts +2 -19
- package/src/createMutation.ts +52 -69
- package/src/createQuery.ts +41 -52
- package/src/createSuspenseInfiniteQuery.ts +1 -22
- package/src/createSuspenseQuery.ts +42 -56
- package/src/hooks/useActorInfiniteQuery.ts +4 -9
- package/src/hooks/useActorMutation.ts +40 -14
- package/src/hooks/useActorSuspenseInfiniteQuery.ts +4 -9
- package/src/types.ts +32 -0
- package/src/utils.ts +52 -0
|
@@ -104,16 +104,11 @@ export const useActorInfiniteQuery = <
|
|
|
104
104
|
T,
|
|
105
105
|
TPageParam
|
|
106
106
|
>): UseActorInfiniteQueryResult<A, M, T, TPageParam> => {
|
|
107
|
-
//
|
|
107
|
+
// Always pass queryKey through generateQueryKey so it is merged with the
|
|
108
|
+
// reactor/function identity. Using the custom key verbatim would cause cache
|
|
109
|
+
// collisions if two different actors or methods share the same key string.
|
|
108
110
|
const baseQueryKey = useMemo(
|
|
109
|
-
() =>
|
|
110
|
-
queryKey ??
|
|
111
|
-
reactor.generateQueryKey(
|
|
112
|
-
{
|
|
113
|
-
functionName,
|
|
114
|
-
},
|
|
115
|
-
callConfig
|
|
116
|
-
),
|
|
111
|
+
() => reactor.generateQueryKey({ functionName, queryKey }, callConfig),
|
|
117
112
|
[queryKey, reactor, functionName, callConfig]
|
|
118
113
|
)
|
|
119
114
|
|
|
@@ -12,6 +12,11 @@ import {
|
|
|
12
12
|
FunctionName,
|
|
13
13
|
TransformKey,
|
|
14
14
|
ReactorReturnErr,
|
|
15
|
+
isCanisterError,
|
|
16
|
+
CanisterError,
|
|
17
|
+
ErrResult,
|
|
18
|
+
ActorMethodReturnType,
|
|
19
|
+
TransformReturnRegistry,
|
|
15
20
|
} from "@ic-reactor/core"
|
|
16
21
|
import { CallConfig } from "@icp-sdk/core/agent"
|
|
17
22
|
|
|
@@ -31,6 +36,17 @@ export interface UseActorMutationParameters<
|
|
|
31
36
|
functionName: M
|
|
32
37
|
callConfig?: CallConfig
|
|
33
38
|
invalidateQueries?: QueryKey[]
|
|
39
|
+
/**
|
|
40
|
+
* Callback for canister-level business logic errors.
|
|
41
|
+
* Called when the canister returns a Result { Err: E } variant.
|
|
42
|
+
* Separate from `onError`, which fires for all errors including network failures.
|
|
43
|
+
*/
|
|
44
|
+
onCanisterError?: (
|
|
45
|
+
error: CanisterError<
|
|
46
|
+
TransformReturnRegistry<ErrResult<ActorMethodReturnType<A[M]>>>[T]
|
|
47
|
+
>,
|
|
48
|
+
variables: ReactorArgs<A, M, T>
|
|
49
|
+
) => void
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
export type UseActorMutationConfig<
|
|
@@ -57,6 +73,7 @@ export type UseActorMutationResult<
|
|
|
57
73
|
* reactor,
|
|
58
74
|
* functionName: "transfer",
|
|
59
75
|
* onSuccess: () => console.log("Success!"),
|
|
76
|
+
* onCanisterError: (err) => console.error("Canister Err:", err.code),
|
|
60
77
|
* })
|
|
61
78
|
*/
|
|
62
79
|
export const useActorMutation = <
|
|
@@ -68,22 +85,17 @@ export const useActorMutation = <
|
|
|
68
85
|
functionName,
|
|
69
86
|
invalidateQueries,
|
|
70
87
|
onSuccess,
|
|
88
|
+
onError,
|
|
89
|
+
onCanisterError,
|
|
71
90
|
callConfig,
|
|
72
91
|
...options
|
|
73
92
|
}: UseActorMutationParameters<A, M, T>): UseActorMutationResult<A, M, T> => {
|
|
74
|
-
// Memoize mutationFn to avoid creating new function on every render
|
|
75
93
|
const mutationFn = useCallback(
|
|
76
|
-
async (args: ReactorArgs<A, M, T>) =>
|
|
77
|
-
|
|
78
|
-
functionName,
|
|
79
|
-
callConfig,
|
|
80
|
-
args,
|
|
81
|
-
})
|
|
82
|
-
},
|
|
94
|
+
async (args: ReactorArgs<A, M, T>) =>
|
|
95
|
+
reactor.callMethod({ functionName, callConfig, args }),
|
|
83
96
|
[reactor, functionName, callConfig]
|
|
84
97
|
)
|
|
85
98
|
|
|
86
|
-
// Memoize onSuccess handler
|
|
87
99
|
const handleSuccess = useCallback(
|
|
88
100
|
async (
|
|
89
101
|
...params: Parameters<
|
|
@@ -103,21 +115,35 @@ export const useActorMutation = <
|
|
|
103
115
|
)
|
|
104
116
|
)
|
|
105
117
|
}
|
|
106
|
-
|
|
107
|
-
await onSuccess(...params)
|
|
108
|
-
}
|
|
118
|
+
await onSuccess?.(...params)
|
|
109
119
|
},
|
|
110
120
|
[reactor, invalidateQueries, onSuccess]
|
|
111
121
|
)
|
|
112
122
|
|
|
113
|
-
|
|
123
|
+
const handleError = useCallback(
|
|
124
|
+
(
|
|
125
|
+
error: ReactorReturnErr<A, M, T>,
|
|
126
|
+
variables: ReactorArgs<A, M, T>,
|
|
127
|
+
context: unknown,
|
|
128
|
+
mutation: unknown
|
|
129
|
+
) => {
|
|
130
|
+
if (isCanisterError(error)) {
|
|
131
|
+
onCanisterError?.(error as any, variables)
|
|
132
|
+
}
|
|
133
|
+
onError?.(error, variables, context as any, mutation as any)
|
|
134
|
+
},
|
|
135
|
+
[onCanisterError, onError]
|
|
136
|
+
)
|
|
137
|
+
|
|
114
138
|
const mutationOptions = useMemo(
|
|
115
139
|
() => ({
|
|
116
140
|
...options,
|
|
117
141
|
mutationFn,
|
|
118
142
|
onSuccess: handleSuccess,
|
|
143
|
+
onError: handleError,
|
|
119
144
|
}),
|
|
120
|
-
|
|
145
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
146
|
+
[mutationFn, handleSuccess, handleError]
|
|
121
147
|
)
|
|
122
148
|
|
|
123
149
|
return useMutation(mutationOptions, reactor.queryClient)
|
|
@@ -107,16 +107,11 @@ export const useActorSuspenseInfiniteQuery = <
|
|
|
107
107
|
T,
|
|
108
108
|
TPageParam
|
|
109
109
|
>): UseActorSuspenseInfiniteQueryResult<A, M, T, TPageParam> => {
|
|
110
|
-
//
|
|
110
|
+
// Always pass queryKey through generateQueryKey so it is merged with the
|
|
111
|
+
// reactor/function identity. Using the custom key verbatim would cause cache
|
|
112
|
+
// collisions if two different actors or methods share the same key string.
|
|
111
113
|
const baseQueryKey = useMemo(
|
|
112
|
-
() =>
|
|
113
|
-
queryKey ??
|
|
114
|
-
reactor.generateQueryKey(
|
|
115
|
-
{
|
|
116
|
-
functionName,
|
|
117
|
-
},
|
|
118
|
-
callConfig
|
|
119
|
-
),
|
|
114
|
+
() => reactor.generateQueryKey({ functionName, queryKey }, callConfig),
|
|
120
115
|
[queryKey, reactor, functionName, callConfig]
|
|
121
116
|
)
|
|
122
117
|
|
package/src/types.ts
CHANGED
|
@@ -231,6 +231,18 @@ export interface BaseQueryResult<
|
|
|
231
231
|
/** Fetch data in loader (uses ensureQueryData for cache-first) */
|
|
232
232
|
fetch: () => Promise<TSelected>
|
|
233
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Eagerly prefetch data into the cache without blocking.
|
|
236
|
+
* Useful for preloading data before navigating to a route.
|
|
237
|
+
*
|
|
238
|
+
* Unlike `fetch()`, this returns a void promise so it can be fire-and-forget.
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* // In a route hover handler
|
|
242
|
+
* button.addEventListener("mouseenter", () => userQuery.prefetch())
|
|
243
|
+
*/
|
|
244
|
+
prefetch: () => Promise<void>
|
|
245
|
+
|
|
234
246
|
/** Invalidate the cache (refetches if query is active) */
|
|
235
247
|
invalidate: () => Promise<void>
|
|
236
248
|
|
|
@@ -256,6 +268,26 @@ export interface BaseQueryResult<
|
|
|
256
268
|
(): TSelected | undefined
|
|
257
269
|
<TFinal>(select: (data: TSelected) => TFinal): TFinal | undefined
|
|
258
270
|
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Write raw data directly into the cache (useful for optimistic updates).
|
|
274
|
+
* Accepts a new value or an updater function that receives the current cached raw data.
|
|
275
|
+
*
|
|
276
|
+
* Note: The value is stored as raw (pre-select) data. Any active `select`
|
|
277
|
+
* transformations are automatically re-applied by React Query on the next render.
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* // Optimistic update before a mutation
|
|
281
|
+
* userQuery.setData({ id: "1", name: "Alice" })
|
|
282
|
+
*
|
|
283
|
+
* // Functional update
|
|
284
|
+
* counterQuery.setData((prev) => (prev ?? 0) + 1)
|
|
285
|
+
*/
|
|
286
|
+
setData: (
|
|
287
|
+
updater:
|
|
288
|
+
| TQueryFnData
|
|
289
|
+
| ((old: TQueryFnData | undefined) => TQueryFnData | undefined)
|
|
290
|
+
) => TQueryFnData | undefined
|
|
259
291
|
}
|
|
260
292
|
|
|
261
293
|
/**
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared internal utilities for query and mutation factories.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { QueryKey } from "@tanstack/react-query"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Internal query-key segment used to distinguish per-call factory args
|
|
9
|
+
* from the base query key. Not part of the public API.
|
|
10
|
+
*/
|
|
11
|
+
export const FACTORY_KEY_ARGS_QUERY_KEY = "__ic_reactor_factory_key_args"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Merge a base query key, optional per-call query key, and optional key-args
|
|
15
|
+
* into a single query key array.
|
|
16
|
+
*
|
|
17
|
+
* Used by createInfiniteQueryFactory and createSuspenseInfiniteQueryFactory to
|
|
18
|
+
* ensure each unique set of factory args produces a distinct cache entry.
|
|
19
|
+
*/
|
|
20
|
+
export function mergeFactoryQueryKey(
|
|
21
|
+
baseQueryKey?: QueryKey,
|
|
22
|
+
callQueryKey?: QueryKey,
|
|
23
|
+
keyArgs?: unknown
|
|
24
|
+
): QueryKey | undefined {
|
|
25
|
+
const merged: unknown[] = []
|
|
26
|
+
|
|
27
|
+
if (baseQueryKey) merged.push(...baseQueryKey)
|
|
28
|
+
if (callQueryKey) merged.push(...callQueryKey)
|
|
29
|
+
if (keyArgs !== undefined)
|
|
30
|
+
merged.push({ [FACTORY_KEY_ARGS_QUERY_KEY]: keyArgs })
|
|
31
|
+
|
|
32
|
+
return merged.length > 0 ? merged : undefined
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build a chained select function that first applies the config-level select
|
|
37
|
+
* (if any) and then the hook-level select (if any).
|
|
38
|
+
*
|
|
39
|
+
* This enables `createQuery` / `createSuspenseQuery` to support two-level
|
|
40
|
+
* select chaining without duplicating the logic.
|
|
41
|
+
*/
|
|
42
|
+
export function buildChainedSelect<TData, TSelected, TFinal = TSelected>(
|
|
43
|
+
configSelect: ((data: TData) => TSelected) | undefined,
|
|
44
|
+
hookSelect: ((data: TSelected) => TFinal) | undefined
|
|
45
|
+
): (rawData: TData) => TSelected | TFinal {
|
|
46
|
+
return (rawData: TData) => {
|
|
47
|
+
const firstPass = configSelect
|
|
48
|
+
? configSelect(rawData)
|
|
49
|
+
: (rawData as unknown as TSelected)
|
|
50
|
+
return hookSelect ? hookSelect(firstPass) : firstPass
|
|
51
|
+
}
|
|
52
|
+
}
|