@ic-reactor/react 3.12.4 → 3.13.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.
- package/README.md +410 -44
- package/dist/auth/auth-client-compat.d.ts +122 -0
- package/dist/auth/auth-client-compat.d.ts.map +1 -0
- package/dist/auth/auth-client-compat.js +162 -0
- package/dist/auth/auth-client-compat.js.map +1 -0
- package/dist/auth/authentication-manager.d.ts +287 -5
- package/dist/auth/authentication-manager.d.ts.map +1 -1
- package/dist/auth/authentication-manager.js +920 -150
- package/dist/auth/authentication-manager.js.map +1 -1
- package/dist/auth/createIdentityAttributeHooks.d.ts.map +1 -1
- package/dist/auth/createIdentityAttributeHooks.js +36 -20
- package/dist/auth/createIdentityAttributeHooks.js.map +1 -1
- package/dist/auth/identity-attributes-manager.d.ts +2 -1
- package/dist/auth/identity-attributes-manager.d.ts.map +1 -1
- package/dist/auth/identity-attributes-manager.js +90 -6
- package/dist/auth/identity-attributes-manager.js.map +1 -1
- package/dist/auth/identity-attributes.d.ts.map +1 -1
- package/dist/auth/identity-attributes.js +57 -0
- package/dist/auth/identity-attributes.js.map +1 -1
- package/dist/auth/local-ii-probe.d.ts +12 -1
- package/dist/auth/local-ii-probe.d.ts.map +1 -1
- package/dist/auth/local-ii-probe.js +22 -3
- package/dist/auth/local-ii-probe.js.map +1 -1
- package/dist/auth/types.d.ts +48 -5
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/createActorHooks.d.ts +9 -20
- package/dist/createActorHooks.d.ts.map +1 -1
- package/dist/createActorHooks.js.map +1 -1
- package/dist/createInfiniteQuery.d.ts +51 -10
- package/dist/createInfiniteQuery.d.ts.map +1 -1
- package/dist/createInfiniteQuery.js +39 -15
- package/dist/createInfiniteQuery.js.map +1 -1
- package/dist/createMutation.d.ts +4 -1
- package/dist/createMutation.d.ts.map +1 -1
- package/dist/createMutation.js +121 -84
- package/dist/createMutation.js.map +1 -1
- package/dist/createQuery.d.ts +35 -2
- package/dist/createQuery.d.ts.map +1 -1
- package/dist/createQuery.js +104 -17
- package/dist/createQuery.js.map +1 -1
- package/dist/createReactorProvider.d.ts +158 -0
- package/dist/createReactorProvider.d.ts.map +1 -0
- package/dist/createReactorProvider.js +256 -0
- package/dist/createReactorProvider.js.map +1 -0
- package/dist/createSuspenseInfiniteQuery.d.ts +16 -9
- package/dist/createSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/createSuspenseInfiniteQuery.js +59 -27
- package/dist/createSuspenseInfiniteQuery.js.map +1 -1
- package/dist/createSuspenseQuery.d.ts +23 -2
- package/dist/createSuspenseQuery.d.ts.map +1 -1
- package/dist/createSuspenseQuery.js +68 -21
- package/dist/createSuspenseQuery.js.map +1 -1
- package/dist/defineDisplayReactor.d.ts +43 -0
- package/dist/defineDisplayReactor.d.ts.map +1 -0
- package/dist/defineDisplayReactor.js +42 -0
- package/dist/defineDisplayReactor.js.map +1 -0
- package/dist/defineReactor.d.ts +46 -72
- package/dist/defineReactor.d.ts.map +1 -1
- package/dist/defineReactor.js +11 -176
- package/dist/defineReactor.js.map +1 -1
- package/dist/defineReactorShared.d.ts +84 -0
- package/dist/defineReactorShared.d.ts.map +1 -0
- package/dist/defineReactorShared.js +139 -0
- package/dist/defineReactorShared.js.map +1 -0
- package/dist/hooks/createAuthHooks.d.ts +9 -2
- package/dist/hooks/createAuthHooks.d.ts.map +1 -1
- package/dist/hooks/createAuthHooks.js +184 -24
- package/dist/hooks/createAuthHooks.js.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.d.ts +36 -8
- package/dist/hooks/useActorInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorInfiniteQuery.js +54 -21
- package/dist/hooks/useActorInfiniteQuery.js.map +1 -1
- package/dist/hooks/useActorMethod.d.ts +37 -4
- package/dist/hooks/useActorMethod.d.ts.map +1 -1
- package/dist/hooks/useActorMethod.js +201 -57
- package/dist/hooks/useActorMethod.js.map +1 -1
- package/dist/hooks/useActorMutation.d.ts +15 -12
- package/dist/hooks/useActorMutation.d.ts.map +1 -1
- package/dist/hooks/useActorMutation.js +14 -13
- package/dist/hooks/useActorMutation.js.map +1 -1
- package/dist/hooks/useActorQuery.d.ts +17 -4
- package/dist/hooks/useActorQuery.d.ts.map +1 -1
- package/dist/hooks/useActorQuery.js +30 -9
- package/dist/hooks/useActorQuery.js.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +17 -5
- package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +1 -1
- package/dist/hooks/useActorSuspenseInfiniteQuery.js +37 -17
- package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +1 -1
- package/dist/hooks/useActorSuspenseQuery.d.ts +2 -2
- package/dist/hooks/useActorSuspenseQuery.d.ts.map +1 -1
- package/dist/hooks/useActorSuspenseQuery.js +20 -9
- package/dist/hooks/useActorSuspenseQuery.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/ownedAuthentication.d.ts +52 -0
- package/dist/ownedAuthentication.d.ts.map +1 -0
- package/dist/ownedAuthentication.js +49 -0
- package/dist/ownedAuthentication.js.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +23 -0
- package/dist/server.js.map +1 -0
- package/dist/testing.d.ts +19 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +19 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +428 -21
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/utils.d.ts +159 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +301 -1
- package/dist/utils.js.map +1 -1
- package/dist/validation.d.ts +12 -7
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +34 -15
- package/dist/validation.js.map +1 -1
- package/llms.txt +259 -33
- package/package.json +17 -5
- package/src/auth/auth-client-compat.ts +273 -0
- package/src/auth/authentication-manager.ts +918 -96
- package/src/auth/createIdentityAttributeHooks.ts +47 -21
- package/src/auth/identity-attributes-manager.ts +100 -5
- package/src/auth/identity-attributes.ts +75 -0
- package/src/auth/local-ii-probe.ts +29 -3
- package/src/auth/types.ts +49 -6
- package/src/createActorHooks.ts +50 -42
- package/src/createInfiniteQuery.ts +120 -28
- package/src/createMutation.ts +213 -132
- package/src/createQuery.ts +164 -32
- package/src/createReactorProvider.ts +365 -0
- package/src/createSuspenseInfiniteQuery.ts +93 -43
- package/src/createSuspenseQuery.ts +102 -32
- package/src/defineDisplayReactor.ts +62 -0
- package/src/defineReactor.ts +81 -263
- package/src/defineReactorShared.ts +268 -0
- package/src/hooks/createAuthHooks.ts +210 -28
- package/src/hooks/useActorInfiniteQuery.ts +156 -55
- package/src/hooks/useActorMethod.ts +295 -92
- package/src/hooks/useActorMutation.ts +42 -30
- package/src/hooks/useActorQuery.ts +43 -10
- package/src/hooks/useActorSuspenseInfiniteQuery.ts +110 -54
- package/src/hooks/useActorSuspenseQuery.ts +30 -15
- package/src/index.ts +8 -0
- package/src/ownedAuthentication.ts +81 -0
- package/src/server.ts +23 -0
- package/src/testing.ts +18 -0
- package/src/types.ts +492 -22
- package/src/utils.ts +387 -3
- package/src/validation.ts +43 -19
package/dist/validation.js
CHANGED
|
@@ -7,30 +7,48 @@
|
|
|
7
7
|
import { ValidationError, isValidationError, } from "@ic-reactor/core";
|
|
8
8
|
// Re-export for convenience
|
|
9
9
|
export { isValidationError, ValidationError };
|
|
10
|
+
/**
|
|
11
|
+
* The field an issue belongs to: the first segment of its path, as a string.
|
|
12
|
+
* An issue with an empty path is about the whole argument (a zod object-level
|
|
13
|
+
* `.refine()`, or any issue of a primitive argument) and belongs to `""`.
|
|
14
|
+
*/
|
|
15
|
+
function fieldNameOf(issue) {
|
|
16
|
+
return String(issue.path[0] ?? "");
|
|
17
|
+
}
|
|
10
18
|
export function mapValidationErrors(error, options) {
|
|
19
|
+
// Collected in a Map, not a plain object: a field named like an
|
|
20
|
+
// Object.prototype member (`constructor`, `toString`, `__proto__`) read that
|
|
21
|
+
// member back as if the field were already filled, which dropped its issue,
|
|
22
|
+
// and threw on `.push` with `multiple`. `Object.fromEntries` then creates
|
|
23
|
+
// every field as an own property, `__proto__` included.
|
|
11
24
|
if (options?.multiple) {
|
|
12
|
-
const
|
|
25
|
+
const messages = new Map();
|
|
13
26
|
for (const issue of error.issues) {
|
|
14
|
-
const fieldName =
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
const fieldName = fieldNameOf(issue);
|
|
28
|
+
const fieldMessages = messages.get(fieldName);
|
|
29
|
+
if (fieldMessages) {
|
|
30
|
+
fieldMessages.push(issue.message);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
messages.set(fieldName, [issue.message]);
|
|
17
34
|
}
|
|
18
|
-
result[fieldName].push(issue.message);
|
|
19
35
|
}
|
|
20
|
-
return
|
|
36
|
+
return Object.fromEntries(messages);
|
|
21
37
|
}
|
|
22
|
-
const
|
|
38
|
+
const messages = new Map();
|
|
23
39
|
for (const issue of error.issues) {
|
|
24
|
-
const fieldName =
|
|
25
|
-
if (!
|
|
26
|
-
|
|
40
|
+
const fieldName = fieldNameOf(issue);
|
|
41
|
+
if (!messages.get(fieldName)) {
|
|
42
|
+
messages.set(fieldName, issue.message);
|
|
27
43
|
}
|
|
28
44
|
}
|
|
29
|
-
return
|
|
45
|
+
return Object.fromEntries(messages);
|
|
30
46
|
}
|
|
31
47
|
/**
|
|
32
48
|
* Gets error message for a specific field from a ValidationError.
|
|
33
|
-
* Returns undefined if no error exists for that field.
|
|
49
|
+
* Returns undefined if no error exists for that field. Pass `""` for issues
|
|
50
|
+
* about the whole argument (an empty path), the key `mapValidationErrors`
|
|
51
|
+
* files them under.
|
|
34
52
|
*
|
|
35
53
|
* @example
|
|
36
54
|
* ```tsx
|
|
@@ -41,12 +59,13 @@ export function mapValidationErrors(error, options) {
|
|
|
41
59
|
* ```
|
|
42
60
|
*/
|
|
43
61
|
export function getFieldError(error, fieldName) {
|
|
44
|
-
const issue = error.issues.find((i) =>
|
|
62
|
+
const issue = error.issues.find((i) => fieldNameOf(i) === fieldName);
|
|
45
63
|
return issue?.message;
|
|
46
64
|
}
|
|
47
65
|
/**
|
|
48
66
|
* Gets all error messages for a specific field from a ValidationError.
|
|
49
|
-
* Returns empty array if no errors exist for that field.
|
|
67
|
+
* Returns empty array if no errors exist for that field. Pass `""` for issues
|
|
68
|
+
* about the whole argument (an empty path).
|
|
50
69
|
*
|
|
51
70
|
* @example
|
|
52
71
|
* ```tsx
|
|
@@ -58,7 +77,7 @@ export function getFieldError(error, fieldName) {
|
|
|
58
77
|
*/
|
|
59
78
|
export function getFieldErrors(error, fieldName) {
|
|
60
79
|
return error.issues
|
|
61
|
-
.filter((i) =>
|
|
80
|
+
.filter((i) => fieldNameOf(i) === fieldName)
|
|
62
81
|
.map((i) => i.message);
|
|
63
82
|
}
|
|
64
83
|
/**
|
package/dist/validation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,eAAe,EACf,iBAAiB,GAElB,MAAM,kBAAkB,CAAA;AAEzB,4BAA4B;AAC5B,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,eAAe,EACf,iBAAiB,GAElB,MAAM,kBAAkB,CAAA;AAEzB,4BAA4B;AAC5B,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,CAAA;AA0B7C;;;;GAIG;AACH,SAAS,WAAW,CAAC,KAAsB;IACzC,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;AACpC,CAAC;AAsCD,MAAM,UAAU,mBAAmB,CACjC,KAAsB,EACtB,OAAoC;IAEpC,gEAAgE;IAChE,6EAA6E;IAC7E,4EAA4E;IAC5E,0EAA0E;IAC1E,wDAAwD;IACxD,IAAI,OAAO,EAAE,QAAQ,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAA;QAC5C,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;YACjC,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,CAAA;YACpC,MAAM,aAAa,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YAC7C,IAAI,aAAa,EAAE,CAAC;gBAClB,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;YACnC,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;YAC1C,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;IACrC,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC1C,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,CAAA;QACpC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;QACxC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;AACrC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAC3B,KAAsB,EACtB,SAAiB;IAEjB,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAA;IACpE,OAAO,KAAK,EAAE,OAAO,CAAA;AACvB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAsB,EACtB,SAAiB;IAEjB,OAAO,KAAK,CAAC,MAAM;SAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC;SAC3C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAA;AAC1B,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAc;IACpD,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAA;IACnC,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,qBAAqB,CACnC,cAA6C,EAC7C,YAAqC;IAErC,OAAO,CAAC,KAAY,EAAE,EAAE;QACtB,MAAM,WAAW,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAA;QAClD,IAAI,WAAW,EAAE,CAAC;YAChB,cAAc,CAAC,WAAW,CAAC,CAAA;QAC7B,CAAC;aAAM,IAAI,YAAY,EAAE,CAAC;YACxB,YAAY,CAAC,KAAK,CAAC,CAAA;QACrB,CAAC;IACH,CAAC,CAAA;AACH,CAAC"}
|
package/llms.txt
CHANGED
|
@@ -1,54 +1,280 @@
|
|
|
1
|
-
# @ic-reactor/react
|
|
1
|
+
# @ic-reactor/react
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
> React hooks, reusable query/mutation objects and Internet Identity sign-in
|
|
4
|
+
> for Internet Computer canisters, on TanStack Query. Re-exports all of
|
|
5
|
+
> `@ic-reactor/core`.
|
|
6
|
+
|
|
7
|
+
Applies to `@ic-reactor/react` 3.13.0.
|
|
8
|
+
|
|
9
|
+
Read this file from `node_modules/@ic-reactor/react/llms.txt` when writing
|
|
10
|
+
code against the installed version. The complete guide for every package is
|
|
11
|
+
https://ic-reactor.b3pay.net/llms-full.txt, and behaviour changes are in
|
|
12
|
+
https://github.com/B3Pay/ic-reactor/blob/main/CHANGELOG.md.
|
|
5
13
|
|
|
6
14
|
## Install
|
|
7
15
|
|
|
8
16
|
```bash
|
|
9
17
|
pnpm add @ic-reactor/react @icp-sdk/core @tanstack/react-query
|
|
10
|
-
pnpm add @icp-sdk/auth@^
|
|
18
|
+
pnpm add @icp-sdk/auth@^10 # optional: Internet Identity sign-in
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Needs `@tanstack/react-query` 5.90.2 or later, React 18 or later and
|
|
22
|
+
TypeScript 5.7 or later. Import the runtime (`ClientManager`, `Reactor`,
|
|
23
|
+
`DisplayReactor`, errors, token helpers) from `@ic-reactor/react` as well; do
|
|
24
|
+
not add `@ic-reactor/core` beside it. `@icp-sdk/auth` v10 is recommended and
|
|
25
|
+
v8 still works; on npm, v8 needs
|
|
26
|
+
`{"overrides":{"@icp-sdk/auth":{"@icp-sdk/core":"$@icp-sdk/core"}}}` in
|
|
27
|
+
`package.json`. v7 and v9 are not supported.
|
|
28
|
+
|
|
29
|
+
## Setup and Usage
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
// src/reactor.tsx
|
|
33
|
+
import { defineReactor, isCanisterError, skipToken } from "@ic-reactor/react"
|
|
34
|
+
import { canisterId, idlFactory, type _SERVICE } from "./declarations/backend"
|
|
35
|
+
|
|
36
|
+
export const {
|
|
37
|
+
reactor: backend,
|
|
38
|
+
useActorQuery,
|
|
39
|
+
useActorMutation,
|
|
40
|
+
useAuth,
|
|
41
|
+
} = defineReactor<_SERVICE>({ name: "backend", idlFactory, canisterId })
|
|
42
|
+
|
|
43
|
+
export function Profile({ userId }: { userId?: string }) {
|
|
44
|
+
const profile = useActorQuery({
|
|
45
|
+
functionName: "get_profile",
|
|
46
|
+
args: userId ? [userId] : skipToken, // no call until the id exists
|
|
47
|
+
})
|
|
48
|
+
const rename = useActorMutation({
|
|
49
|
+
functionName: "update_profile",
|
|
50
|
+
invalidateQueries: [{ functionName: "get_profile" }], // refetched first
|
|
51
|
+
onCanisterError: (err) => console.warn("Refused:", err.code), // Err variant
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
if (!userId) return <p>Sign in first</p>
|
|
55
|
+
if (profile.isPending) return <p>Loading…</p>
|
|
56
|
+
if (profile.error) {
|
|
57
|
+
const { error } = profile
|
|
58
|
+
return <p>{isCanisterError(error) ? error.code : error.message}</p>
|
|
59
|
+
}
|
|
60
|
+
return (
|
|
61
|
+
<button
|
|
62
|
+
disabled={rename.isPending}
|
|
63
|
+
onClick={() => rename.mutate([{ name: "Ada" }])}
|
|
64
|
+
>
|
|
65
|
+
{profile.data.name}: {profile.data.likes.toString()} likes
|
|
66
|
+
</button>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`defineReactor` builds the `QueryClient` (query retry `reactorRetry`), the
|
|
72
|
+
`ClientManager`, a `Reactor` with raw Candid values (`bigint`, `Principal`),
|
|
73
|
+
the six hooks of `createActorHooks`, and `useAuth`, `useAgentState`,
|
|
74
|
+
`useUserPrincipal`, `useIdentityAttributes`, `authentication`,
|
|
75
|
+
`identityAttributes`. `defineDisplayReactor` takes the same options and gives
|
|
76
|
+
display values instead (strings for `bigint` and `Principal`, hex for blobs,
|
|
77
|
+
`T | undefined` for `opt`, `{ _type: "A", A: value }` for a variant, and just
|
|
78
|
+
`{ _type: "A" }` for a case without a value). `canisterId` is required except
|
|
79
|
+
on a local replica whose `ic_env` cookie names it. `./declarations/backend`
|
|
80
|
+
stands for your canister's declarations: `idlFactory` from the generated
|
|
81
|
+
`.js`, `_SERVICE` from the `.d.ts` (with the CLI or the Vite plugin,
|
|
82
|
+
`src/declarations/<name>/declarations/<did>.js` and `.d.ts`, named after the
|
|
83
|
+
`.did` file), and `canisterId` your canister's id as text.
|
|
84
|
+
|
|
85
|
+
## When to Use What
|
|
86
|
+
|
|
87
|
+
| Need | Use |
|
|
88
|
+
| --------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
89
|
+
| Client-only app, raw Candid values | `defineReactor(...)` at module scope |
|
|
90
|
+
| Client-only app, values for forms and display | `defineDisplayReactor(...)` at module scope |
|
|
91
|
+
| A reactor you built yourself | `createActorHooks(reactor)` |
|
|
92
|
+
| Server-rendered app (Next.js, any SSR) | `createReactorProvider(() => defineReactor(...))` in a `"use client"` module |
|
|
93
|
+
| React Server Component, server action, route | `ClientManager` + `Reactor` from this package, built in the request; `fetchQuery` |
|
|
94
|
+
| One call used in components and loaders | `createQuery`, `createQueryFactory` (args later), `createMutation` |
|
|
95
|
+
| Suspense | `useActorSuspenseQuery`, `createSuspenseQuery`, `createSuspenseQueryFactory` |
|
|
96
|
+
| Paginated reads | `useActorInfiniteQuery`, `createInfiniteQuery`, `createInfiniteQueryFactory` |
|
|
97
|
+
| One hook for a query or update with `call()` | `useActorMethod` |
|
|
98
|
+
| Another canister with the same interface | `reactor.forCanister(canisterId)`, or `callConfig: { canisterId }` for one query |
|
|
99
|
+
| A second canister on the same sign-in | `defineReactor({ ..., authentication: firstApp.authentication })` |
|
|
100
|
+
| Sign-in with a manual setup | `createAuthHooks(new AuthenticationManager({ clientManager }))` |
|
|
101
|
+
| Tests | `installFakeReplica` + `createTestCanister` from `@ic-reactor/react/testing` |
|
|
102
|
+
|
|
103
|
+
## Inside React vs Outside React
|
|
104
|
+
|
|
105
|
+
- In components and custom hooks: `useActorQuery`, `useActorMutation`,
|
|
106
|
+
`useActorMethod`, `useAuth`, `.useQuery()`, `.useMutation()`,
|
|
107
|
+
`.useSuspenseQuery()`, `.useInfiniteQuery()`.
|
|
108
|
+
- In loaders, actions, services, scripts and non-hook tests: query objects'
|
|
109
|
+
`.fetch()`, `.prefetch()`, `.invalidate()`, `.getCacheData()`,
|
|
110
|
+
`.setData()`, `.cancel()`, `.reset()`, `.optimisticUpdate()`; mutation
|
|
111
|
+
objects' `.execute(args)`; and the reactor's `.fetchQuery()`,
|
|
112
|
+
`.getQueryData()`, `.invalidateQueries()`, `.callMethod()`.
|
|
113
|
+
- `.fetch()` and `reactor.fetchQuery()` fetch again as the new principal when a
|
|
114
|
+
sign-in or sign-out lands mid-fetch; they never reject with TanStack's
|
|
115
|
+
`CancelledError`. `.prefetch()` fetches again the same way and never
|
|
116
|
+
rejects, and a query method's `call()` / `refetch()` from `useActorMethod`
|
|
117
|
+
resolve with the new principal's answer (`undefined` if that fetch fails).
|
|
118
|
+
Wrap your own `queryClient.fetchQuery` or
|
|
119
|
+
`fetchInfiniteQuery` of a canister query in
|
|
120
|
+
`clientManager.fetchAcrossIdentitySwitch(() => ...)`.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { createMutation, createQueryFactory } from "@ic-reactor/react"
|
|
124
|
+
import { backend } from "./reactor"
|
|
125
|
+
|
|
126
|
+
export const getPost = createQueryFactory(backend, { functionName: "get_post" })
|
|
127
|
+
export const likePost = createMutation(backend, {
|
|
128
|
+
functionName: "like_post",
|
|
129
|
+
invalidateQueries: [getPost], // every cached post, whatever its args
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// In a component: getPost([id]).useQuery(), likePost.useMutation()
|
|
133
|
+
// In a loader: await getPost([id]).fetch()
|
|
134
|
+
// In an action: await likePost.execute([id])
|
|
11
135
|
```
|
|
12
136
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
137
|
+
## Server Rendering
|
|
138
|
+
|
|
139
|
+
A reactor owns its `QueryClient` and query keys carry no caller principal, so
|
|
140
|
+
a module-scope reactor on a server is one cache shared by every request. In a
|
|
141
|
+
server-rendered app, build reactors per mounted provider:
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
"use client"
|
|
145
|
+
import { createReactorProvider, defineReactor } from "@ic-reactor/react"
|
|
146
|
+
import { canisterId, idlFactory, type _SERVICE } from "./declarations/backend"
|
|
147
|
+
|
|
148
|
+
export const { ReactorProvider, useReactor } = createReactorProvider(() =>
|
|
149
|
+
defineReactor<_SERVICE>({ name: "backend", idlFactory, canisterId })
|
|
150
|
+
)
|
|
151
|
+
// layout: <ReactorProvider>{children}</ReactorProvider>
|
|
152
|
+
// component: const { useActorQuery, useAuth } = useReactor()
|
|
153
|
+
```
|
|
18
154
|
|
|
19
|
-
|
|
155
|
+
- The factory runs once per mounted provider (once per request on a server).
|
|
156
|
+
It may return a record (`useReactor("ledger")`) and query/mutation objects
|
|
157
|
+
built inside it. `useReactor()` is typed as the factory's return: never write
|
|
158
|
+
`as any` hook forwarders or a hand-rolled context provider.
|
|
159
|
+
- On unmount it disposes the `AuthenticationManager`s built for its value.
|
|
160
|
+
When the value holds exactly one `QueryClient` (its reactors share one
|
|
161
|
+
`ClientManager`), it renders a `QueryClientProvider` for it
|
|
162
|
+
(`{ queryClientProvider: false }` to keep your own).
|
|
163
|
+
- Props other than `children` go to the factory, read once per mount; change
|
|
164
|
+
the provider's `key` to rebuild. A provider mounted by a transition needs a
|
|
165
|
+
`<Suspense>` boundary inside it around suspending components.
|
|
166
|
+
- A React Server Component, server action or route handler may import
|
|
167
|
+
`Reactor`, `DisplayReactor`, `ClientManager` and the rest of the core runtime
|
|
168
|
+
from `@ic-reactor/react`: its `react-server` export condition resolves to an
|
|
169
|
+
entry without React. Hooks, `defineReactor`, `defineDisplayReactor`,
|
|
170
|
+
`createActorHooks`, `createReactorProvider`, the query/mutation factories,
|
|
171
|
+
`skipToken` and the auth classes are missing exports there.
|
|
20
172
|
|
|
21
|
-
|
|
22
|
-
- Existing reactor instance: `createActorHooks(reactor)`
|
|
23
|
-
- Reusable shared operations: `createQuery(...)`, `createMutation(...)`
|
|
173
|
+
## Queries
|
|
24
174
|
|
|
25
|
-
|
|
175
|
+
- `skipToken` in place of args waits without calling the canister:
|
|
176
|
+
`args: owner ? [account] : skipToken` in `useActorQuery`,
|
|
177
|
+
`getArgs: owner ? (page) => [...] : skipToken` in `useActorInfiniteQuery`,
|
|
178
|
+
`getBalance(owner ? [account] : skipToken).useQuery()` with
|
|
179
|
+
`createQueryFactory` (that skipped object has only `useQuery()`). The
|
|
180
|
+
suspense hooks, `createQuery` and `createSuspenseQuery` do not take it.
|
|
181
|
+
- Paginated reads: `createInfiniteQuery` (or `useActorInfiniteQuery`) takes
|
|
182
|
+
`functionName`, `initialPageParam`, `getNextPageParam` and
|
|
183
|
+
`getArgs: (page) => [...] as const`. Keep `as const`: without it
|
|
184
|
+
`createInfiniteQuery` infers an array rather than the argument tuple and
|
|
185
|
+
rejects it.
|
|
186
|
+
- A `Result` is unwrapped: `data` is the `Ok` payload and an `Err` arrives as a
|
|
187
|
+
`CanisterError` in `error`.
|
|
188
|
+
- Query hooks and objects take TanStack's options (`select`, `staleTime`,
|
|
189
|
+
`enabled`, ...) and `callConfig` (`canisterId`, `agent`,
|
|
190
|
+
`effectiveCanisterId`).
|
|
191
|
+
- Several tokens of one interface: `reactor.forCanister(canisterId)` returns a
|
|
192
|
+
memoized sibling reactor on the same `ClientManager`. Build hooks on it with
|
|
193
|
+
`useMemo(() => createActorHooks(ledger.forCanister(id)), [id])`.
|
|
194
|
+
- Types: `ReactorArgsOf<typeof reactor, "method">[0]`,
|
|
195
|
+
`ReactorDataOf<typeof reactor, "method">`,
|
|
196
|
+
`ReactorErrorOf<typeof reactor, "method">`.
|
|
26
197
|
|
|
27
|
-
|
|
28
|
-
`useActorMethod`, `.useQuery()`, `.useMutation()`
|
|
29
|
-
- Outside React (loaders/actions/services/tests): `.fetch()`, `.prefetch()`,
|
|
30
|
-
`.invalidate()`, `.getCacheData()`, `.execute(args)`
|
|
198
|
+
## Mutations, Invalidation and Retries
|
|
31
199
|
|
|
32
|
-
|
|
200
|
+
- Call state-changing update methods only through `useActorMutation`,
|
|
201
|
+
`useActorMethod` or `createMutation`. A query runs its method again on every
|
|
202
|
+
refetch, and each run of an update executes on the canister.
|
|
203
|
+
- `invalidateQueries` takes query objects, query factories (every args
|
|
204
|
+
instance) and `{ functionName, args? }` methods of the mutation's reactor,
|
|
205
|
+
keyed at the canister the mutation was sent to. It is awaited before
|
|
206
|
+
`onSuccess`. Every key starts with the canister id, so a hand-written
|
|
207
|
+
`["get_posts"]` matches nothing.
|
|
208
|
+
- `onCanisterError` receives the `CanisterError` of an `Err` result
|
|
209
|
+
(`err.code`, `err.err`); `onError` receives every error.
|
|
210
|
+
- Mutations retry nothing unless `retry` is set, on them or as the
|
|
211
|
+
`QueryClient`'s `mutations.retry` default (which reaches `execute()` and
|
|
212
|
+
`useActorMethod` update calls too). Use `retry: reactorUpdateRetry`, which
|
|
213
|
+
retries only a `SysTransient` rejection, never a number.
|
|
214
|
+
- Optimistic UI: return `query.optimisticUpdate(updater)` from `onMutate`,
|
|
215
|
+
call `update?.rollback()` in `onError`, and `invalidate()` in `onSettled`.
|
|
216
|
+
When you need the `QueryClient`, use `reactor.queryClient`:
|
|
217
|
+
`useQueryClient()` throws without a `QueryClientProvider`.
|
|
33
218
|
|
|
34
|
-
##
|
|
219
|
+
## Sign-In
|
|
35
220
|
|
|
36
|
-
-
|
|
37
|
-
|
|
38
|
-
|
|
221
|
+
- `useAuth()` gives `login`, `logout`, `isAuthenticated`,
|
|
222
|
+
`isAuthenticating`, `principal`, `identity` and `error`. It reports
|
|
223
|
+
`isAuthenticating: true` until the first session restore settles, and in
|
|
224
|
+
every server render: check it before redirecting on `!isAuthenticated`.
|
|
225
|
+
- `createAuthHooks` takes an `AuthenticationManager`, never a `ClientManager`,
|
|
226
|
+
and returns `useAuth`, `useAgentState` and `useUserPrincipal`.
|
|
227
|
+
`useIdentityAttributes` comes from `createIdentityAttributeHooks`.
|
|
228
|
+
- Pass an identity-attribute nonce as a callback (`nonce: async () => ...`),
|
|
229
|
+
not an awaited value, so the popup opens within the click.
|
|
230
|
+
- `authentication.dispose()` releases the auth client the manager built,
|
|
231
|
+
without signing out.
|
|
39
232
|
|
|
40
|
-
##
|
|
233
|
+
## Testing
|
|
41
234
|
|
|
42
|
-
|
|
43
|
-
|
|
235
|
+
Run the real reactor and hooks against `installFakeReplica` and
|
|
236
|
+
`createTestCanister` from `@ic-reactor/react/testing`. Install the fake before
|
|
237
|
+
any `ClientManager` is built: for a module-scope `defineReactor`, at the top of
|
|
238
|
+
the test file, then `await import(...)` the components. Generated code is
|
|
239
|
+
module scope too: import `idlFactory` and `_SERVICE` from the canister's
|
|
240
|
+
`declarations/` folder, key the fake by the `canisterId` the generator wrote,
|
|
241
|
+
and `await import(...)` the entry after the fake. A handler returns
|
|
242
|
+
`{ Err: ... }` for a `CanisterError` and throws for a `CallError`. Reset a
|
|
243
|
+
shared reactor with `queryClient.clear()`, and test a signed-in user with
|
|
244
|
+
`clientManager.updateAgent(identity)`.
|
|
44
245
|
|
|
45
|
-
##
|
|
246
|
+
## Do Not
|
|
46
247
|
|
|
47
|
-
|
|
48
|
-
`
|
|
248
|
+
- Call hooks outside components and custom hooks, or create hooks with
|
|
249
|
+
`createActorHooks` / `createAuthHooks` on every render.
|
|
250
|
+
- Put a state-changing update method in `useActorQuery`, `createQuery` or
|
|
251
|
+
`createQueryFactory`.
|
|
252
|
+
- Give an update mutation a numeric `retry`; use `reactorUpdateRetry`.
|
|
253
|
+
- Hand-write query keys, or add the canister id to a `queryKey`.
|
|
254
|
+
- Write `args: [userId!]`, a placeholder account, or `as any` plus `enabled`;
|
|
255
|
+
pass `skipToken`.
|
|
256
|
+
- Check `"Ok" in data`; the `Result` is already unwrapped.
|
|
257
|
+
- Use `Number(x) / 10 ** decimals`; use `formatTokenAmount` and
|
|
258
|
+
`parseTokenAmount`.
|
|
259
|
+
- Retarget a shared reactor with `setCanisterId`; use `forCanister`.
|
|
260
|
+
- Write `defineReactor({ display: true })`; use `defineDisplayReactor`.
|
|
261
|
+
- Build reactors or managers at module scope in a server-rendered app, or
|
|
262
|
+
import hooks into a server component.
|
|
263
|
+
- Stub a reactor with `as unknown as Reactor` in tests.
|
|
264
|
+
- Edit `index.generated.ts` or `index.factories.generated.ts`.
|
|
49
265
|
|
|
50
|
-
##
|
|
266
|
+
## Docs
|
|
51
267
|
|
|
52
|
-
-
|
|
53
|
-
- React
|
|
54
|
-
-
|
|
268
|
+
- React package: https://ic-reactor.b3pay.net/v3/packages/react.md
|
|
269
|
+
- React setup: https://ic-reactor.b3pay.net/v3/framework/react-setup.md
|
|
270
|
+
- Queries: https://ic-reactor.b3pay.net/v3/framework/queries.md
|
|
271
|
+
- Mutations: https://ic-reactor.b3pay.net/v3/framework/mutations.md
|
|
272
|
+
- Query caching: https://ic-reactor.b3pay.net/v3/framework/query-caching.md
|
|
273
|
+
- Query and mutation factories: https://ic-reactor.b3pay.net/v3/reference/factories/overview.md
|
|
274
|
+
- createReactorProvider: https://ic-reactor.b3pay.net/v3/reference/createReactorProvider.md
|
|
275
|
+
- Authentication: https://ic-reactor.b3pay.net/v3/guides/authentication.md
|
|
276
|
+
- Error handling: https://ic-reactor.b3pay.net/v3/guides/error-handling.md
|
|
277
|
+
- Testing: https://ic-reactor.b3pay.net/v3/guides/testing.md
|
|
278
|
+
- Index of all docs: https://ic-reactor.b3pay.net/llms.txt
|
|
279
|
+
- Full guide: https://ic-reactor.b3pay.net/llms-full.txt
|
|
280
|
+
- Agent skill for Claude Code and other agents: https://github.com/B3Pay/ic-reactor/tree/main/skill-packages/ic-reactor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ic-reactor/react",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
4
4
|
"description": "IC Reactor React library for building Internet Computer apps",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -13,9 +13,15 @@
|
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
|
+
"react-server": "./dist/server.js",
|
|
16
17
|
"import": "./dist/index.js",
|
|
17
18
|
"default": "./dist/index.js"
|
|
18
19
|
},
|
|
20
|
+
"./testing": {
|
|
21
|
+
"types": "./dist/testing.d.ts",
|
|
22
|
+
"import": "./dist/testing.js",
|
|
23
|
+
"default": "./dist/testing.js"
|
|
24
|
+
},
|
|
19
25
|
"./package.json": "./package.json"
|
|
20
26
|
},
|
|
21
27
|
"files": [
|
|
@@ -53,18 +59,21 @@
|
|
|
53
59
|
"author": "Behrad Deylami",
|
|
54
60
|
"license": "MIT",
|
|
55
61
|
"dependencies": {
|
|
56
|
-
"@ic-reactor/core": "^3.
|
|
62
|
+
"@ic-reactor/core": "^3.13.0"
|
|
57
63
|
},
|
|
58
64
|
"peerDependencies": {
|
|
59
|
-
"@icp-sdk/auth": "^8.0.0",
|
|
65
|
+
"@icp-sdk/auth": "^8.0.0 || ^10.0.0",
|
|
60
66
|
"@icp-sdk/core": "^6.1.0",
|
|
61
|
-
"@
|
|
67
|
+
"@noble/curves": "^2.2.0",
|
|
68
|
+
"@tanstack/react-query": "^5.90.2",
|
|
62
69
|
"react": ">=18.0.0",
|
|
63
70
|
"react-dom": ">=18.0.0"
|
|
64
71
|
},
|
|
65
72
|
"devDependencies": {
|
|
66
|
-
"@icp-sdk/auth": "^
|
|
73
|
+
"@icp-sdk/auth": "^10.0.0",
|
|
74
|
+
"@icp-sdk/auth-v8": "npm:@icp-sdk/auth@^8.0.3",
|
|
67
75
|
"@icp-sdk/core": "^6.1.0",
|
|
76
|
+
"@noble/curves": "^2.2.0",
|
|
68
77
|
"@size-limit/preset-small-lib": "^13.0.3",
|
|
69
78
|
"@tanstack/react-query": "^5.102.8",
|
|
70
79
|
"@testing-library/dom": "^10.4.1",
|
|
@@ -98,6 +107,9 @@
|
|
|
98
107
|
"@icp-sdk/auth": {
|
|
99
108
|
"optional": true
|
|
100
109
|
},
|
|
110
|
+
"@noble/curves": {
|
|
111
|
+
"optional": true
|
|
112
|
+
},
|
|
101
113
|
"react-dom": {
|
|
102
114
|
"optional": true
|
|
103
115
|
}
|