@ic-reactor/react 3.12.2 → 3.12.4
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 +2 -1
- package/dist/auth/authentication-manager.d.ts +11 -1
- package/dist/auth/authentication-manager.d.ts.map +1 -1
- package/dist/auth/authentication-manager.js +49 -7
- package/dist/auth/authentication-manager.js.map +1 -1
- package/dist/createActorHooks.d.ts +17 -6
- package/dist/createActorHooks.d.ts.map +1 -1
- package/dist/createActorHooks.js.map +1 -1
- package/dist/createMutation.d.ts.map +1 -1
- package/dist/createMutation.js +33 -7
- package/dist/createMutation.js.map +1 -1
- package/dist/defineReactor.js +3 -4
- package/dist/defineReactor.js.map +1 -1
- package/dist/hooks/createAuthHooks.d.ts +5 -0
- package/dist/hooks/createAuthHooks.d.ts.map +1 -1
- package/dist/hooks/createAuthHooks.js +18 -4
- package/dist/hooks/createAuthHooks.js.map +1 -1
- package/dist/hooks/useActorMethod.d.ts +21 -11
- package/dist/hooks/useActorMethod.d.ts.map +1 -1
- package/dist/hooks/useActorMethod.js +29 -14
- package/dist/hooks/useActorMethod.js.map +1 -1
- package/dist/hooks/useActorMutation.d.ts.map +1 -1
- package/dist/hooks/useActorMutation.js +5 -2
- package/dist/hooks/useActorMutation.js.map +1 -1
- package/package.json +7 -8
- package/src/auth/authentication-manager.ts +53 -7
- package/src/createActorHooks.ts +34 -13
- package/src/createMutation.ts +39 -14
- package/src/defineReactor.ts +4 -3
- package/src/hooks/createAuthHooks.ts +30 -7
- package/src/hooks/useActorMethod.ts +79 -38
- package/src/hooks/useActorMutation.ts +5 -2
|
@@ -11,6 +11,11 @@ export interface UseAuthReturn {
|
|
|
11
11
|
logout: (options?: { returnTo?: string }) => Promise<void>
|
|
12
12
|
isAuthenticated: boolean
|
|
13
13
|
isAuthenticating: boolean
|
|
14
|
+
/**
|
|
15
|
+
* The signed-in user's principal, or `null` while signed out. A signed-out
|
|
16
|
+
* session still holds an anonymous `identity`, and its principal is not
|
|
17
|
+
* returned here.
|
|
18
|
+
*/
|
|
14
19
|
principal: Principal | null
|
|
15
20
|
identity: Identity | null
|
|
16
21
|
error: Error | undefined
|
|
@@ -22,6 +27,26 @@ export interface CreateAuthHooksReturn {
|
|
|
22
27
|
useAuth: () => UseAuthReturn
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The principal both hooks return.
|
|
32
|
+
*
|
|
33
|
+
* `authenticate()` and `logout()` leave the client's anonymous identity in
|
|
34
|
+
* `authState.identity` with `isAuthenticated: false`, so deriving the principal
|
|
35
|
+
* from the identity alone reported `2vxsx-fae` for a signed-out user, and
|
|
36
|
+
* `principal ? <SignedIn /> : <SignedOut />` rendered the signed-in branch.
|
|
37
|
+
* Memoized on the identity, because `getPrincipal()` may build a new object on
|
|
38
|
+
* each call and the result is often a hook dependency.
|
|
39
|
+
*/
|
|
40
|
+
function usePrincipal(
|
|
41
|
+
isAuthenticated: boolean,
|
|
42
|
+
identity: Identity | null
|
|
43
|
+
): Principal | null {
|
|
44
|
+
return useMemo(
|
|
45
|
+
() => (isAuthenticated && identity ? identity.getPrincipal() : null),
|
|
46
|
+
[isAuthenticated, identity]
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
25
50
|
/**
|
|
26
51
|
* Create authentication hooks for managing user sessions with Internet Identity.
|
|
27
52
|
*
|
|
@@ -125,10 +150,7 @@ export const createAuthHooks = (
|
|
|
125
150
|
}
|
|
126
151
|
}, [])
|
|
127
152
|
|
|
128
|
-
const principal =
|
|
129
|
-
() => (identity ? identity.getPrincipal() : null),
|
|
130
|
-
[identity]
|
|
131
|
-
)
|
|
153
|
+
const principal = usePrincipal(isAuthenticated, identity)
|
|
132
154
|
|
|
133
155
|
return {
|
|
134
156
|
authenticate,
|
|
@@ -144,7 +166,8 @@ export const createAuthHooks = (
|
|
|
144
166
|
|
|
145
167
|
/**
|
|
146
168
|
* Get the current user's Principal.
|
|
147
|
-
* Returns null if not authenticated
|
|
169
|
+
* Returns null if not authenticated, including while the signed-out session
|
|
170
|
+
* holds the anonymous identity.
|
|
148
171
|
*
|
|
149
172
|
* @example
|
|
150
173
|
* function UserInfo() {
|
|
@@ -154,8 +177,8 @@ export const createAuthHooks = (
|
|
|
154
177
|
* }
|
|
155
178
|
*/
|
|
156
179
|
const useUserPrincipal = (): Principal | null => {
|
|
157
|
-
const { identity } = useAuthState()
|
|
158
|
-
return
|
|
180
|
+
const { isAuthenticated, identity } = useAuthState()
|
|
181
|
+
return usePrincipal(isAuthenticated, identity)
|
|
159
182
|
}
|
|
160
183
|
|
|
161
184
|
return {
|
|
@@ -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?: (
|
|
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
|
-
/**
|
|
98
|
-
|
|
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<
|
|
140
|
+
) => Promise<
|
|
141
|
+
ReactorQueryData<ReactorReturnOk<Service, Method, Transform>> | undefined
|
|
142
|
+
>
|
|
129
143
|
|
|
130
144
|
/**
|
|
131
|
-
* Reset the state (
|
|
132
|
-
* For queries:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
312
|
+
TQueryData,
|
|
286
313
|
ReactorReturnErr<Service, Method, Transform>,
|
|
287
314
|
ReactorArgs<Service, Method, Transform>
|
|
288
315
|
>(
|
|
289
316
|
{
|
|
290
317
|
mutationKey: queryKey,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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<
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -153,7 +153,7 @@ export const useActorMutation = <
|
|
|
153
153
|
)
|
|
154
154
|
|
|
155
155
|
const handleError = useCallback(
|
|
156
|
-
(
|
|
156
|
+
async (
|
|
157
157
|
error: ReactorReturnErr<Service, Method, Transform>,
|
|
158
158
|
variables: ReactorArgs<Service, Method, Transform>,
|
|
159
159
|
context: unknown,
|
|
@@ -162,7 +162,10 @@ export const useActorMutation = <
|
|
|
162
162
|
if (isCanisterError(error)) {
|
|
163
163
|
onCanisterError?.(error as any, variables)
|
|
164
164
|
}
|
|
165
|
-
|
|
165
|
+
// Awaited like `onSuccess` above. TanStack Query waits for the promise
|
|
166
|
+
// `onError` returns before it runs `onSettled` and settles the mutation,
|
|
167
|
+
// and it can only wait for a promise this wrapper passes back.
|
|
168
|
+
await onError?.(error, variables, context as any, mutation as any)
|
|
166
169
|
},
|
|
167
170
|
[onCanisterError, onError]
|
|
168
171
|
)
|