@ic-reactor/react 3.13.0 → 4.0.0-beta.2

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 (174) hide show
  1. package/README.md +237 -787
  2. package/dist/index.d.ts +260 -16
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +471 -23
  5. package/dist/index.js.map +1 -1
  6. package/llms.txt +82 -278
  7. package/package.json +11 -39
  8. package/src/index.tsx +612 -0
  9. package/dist/auth/auth-client-compat.d.ts +0 -122
  10. package/dist/auth/auth-client-compat.d.ts.map +0 -1
  11. package/dist/auth/auth-client-compat.js +0 -162
  12. package/dist/auth/auth-client-compat.js.map +0 -1
  13. package/dist/auth/authentication-manager.d.ts +0 -405
  14. package/dist/auth/authentication-manager.d.ts.map +0 -1
  15. package/dist/auth/authentication-manager.js +0 -1537
  16. package/dist/auth/authentication-manager.js.map +0 -1
  17. package/dist/auth/constants.d.ts +0 -24
  18. package/dist/auth/constants.d.ts.map +0 -1
  19. package/dist/auth/constants.js +0 -24
  20. package/dist/auth/constants.js.map +0 -1
  21. package/dist/auth/createIdentityAttributeHooks.d.ts +0 -14
  22. package/dist/auth/createIdentityAttributeHooks.d.ts.map +0 -1
  23. package/dist/auth/createIdentityAttributeHooks.js +0 -122
  24. package/dist/auth/createIdentityAttributeHooks.js.map +0 -1
  25. package/dist/auth/identity-attributes-manager.d.ts +0 -27
  26. package/dist/auth/identity-attributes-manager.d.ts.map +0 -1
  27. package/dist/auth/identity-attributes-manager.js +0 -191
  28. package/dist/auth/identity-attributes-manager.js.map +0 -1
  29. package/dist/auth/identity-attributes.d.ts +0 -19
  30. package/dist/auth/identity-attributes.d.ts.map +0 -1
  31. package/dist/auth/identity-attributes.js +0 -227
  32. package/dist/auth/identity-attributes.js.map +0 -1
  33. package/dist/auth/index.d.ts +0 -8
  34. package/dist/auth/index.d.ts.map +0 -1
  35. package/dist/auth/index.js +0 -8
  36. package/dist/auth/index.js.map +0 -1
  37. package/dist/auth/local-ii-probe.d.ts +0 -57
  38. package/dist/auth/local-ii-probe.d.ts.map +0 -1
  39. package/dist/auth/local-ii-probe.js +0 -121
  40. package/dist/auth/local-ii-probe.js.map +0 -1
  41. package/dist/auth/types.d.ts +0 -222
  42. package/dist/auth/types.d.ts.map +0 -1
  43. package/dist/auth/types.js +0 -2
  44. package/dist/auth/types.js.map +0 -1
  45. package/dist/createActorHooks.d.ts +0 -41
  46. package/dist/createActorHooks.d.ts.map +0 -1
  47. package/dist/createActorHooks.js +0 -17
  48. package/dist/createActorHooks.js.map +0 -1
  49. package/dist/createInfiniteQuery.d.ts +0 -185
  50. package/dist/createInfiniteQuery.d.ts.map +0 -1
  51. package/dist/createInfiniteQuery.js +0 -198
  52. package/dist/createInfiniteQuery.js.map +0 -1
  53. package/dist/createMutation.d.ts +0 -33
  54. package/dist/createMutation.d.ts.map +0 -1
  55. package/dist/createMutation.js +0 -199
  56. package/dist/createMutation.js.map +0 -1
  57. package/dist/createQuery.d.ts +0 -63
  58. package/dist/createQuery.d.ts.map +0 -1
  59. package/dist/createQuery.js +0 -204
  60. package/dist/createQuery.js.map +0 -1
  61. package/dist/createReactorProvider.d.ts +0 -158
  62. package/dist/createReactorProvider.d.ts.map +0 -1
  63. package/dist/createReactorProvider.js +0 -256
  64. package/dist/createReactorProvider.js.map +0 -1
  65. package/dist/createSuspenseInfiniteQuery.d.ts +0 -154
  66. package/dist/createSuspenseInfiniteQuery.d.ts.map +0 -1
  67. package/dist/createSuspenseInfiniteQuery.js +0 -209
  68. package/dist/createSuspenseInfiniteQuery.js.map +0 -1
  69. package/dist/createSuspenseQuery.d.ts +0 -46
  70. package/dist/createSuspenseQuery.d.ts.map +0 -1
  71. package/dist/createSuspenseQuery.js +0 -158
  72. package/dist/createSuspenseQuery.js.map +0 -1
  73. package/dist/defineDisplayReactor.d.ts +0 -43
  74. package/dist/defineDisplayReactor.d.ts.map +0 -1
  75. package/dist/defineDisplayReactor.js +0 -42
  76. package/dist/defineDisplayReactor.js.map +0 -1
  77. package/dist/defineReactor.d.ts +0 -99
  78. package/dist/defineReactor.d.ts.map +0 -1
  79. package/dist/defineReactor.js +0 -15
  80. package/dist/defineReactor.js.map +0 -1
  81. package/dist/defineReactorShared.d.ts +0 -84
  82. package/dist/defineReactorShared.d.ts.map +0 -1
  83. package/dist/defineReactorShared.js +0 -139
  84. package/dist/defineReactorShared.js.map +0 -1
  85. package/dist/hooks/createAuthHooks.d.ts +0 -50
  86. package/dist/hooks/createAuthHooks.d.ts.map +0 -1
  87. package/dist/hooks/createAuthHooks.js +0 -291
  88. package/dist/hooks/createAuthHooks.js.map +0 -1
  89. package/dist/hooks/index.d.ts +0 -21
  90. package/dist/hooks/index.d.ts.map +0 -1
  91. package/dist/hooks/index.js +0 -24
  92. package/dist/hooks/index.js.map +0 -1
  93. package/dist/hooks/useActorInfiniteQuery.d.ts +0 -67
  94. package/dist/hooks/useActorInfiniteQuery.d.ts.map +0 -1
  95. package/dist/hooks/useActorInfiniteQuery.js +0 -89
  96. package/dist/hooks/useActorInfiniteQuery.js.map +0 -1
  97. package/dist/hooks/useActorMethod.d.ts +0 -148
  98. package/dist/hooks/useActorMethod.d.ts.map +0 -1
  99. package/dist/hooks/useActorMethod.js +0 -394
  100. package/dist/hooks/useActorMethod.js.map +0 -1
  101. package/dist/hooks/useActorMutation.d.ts +0 -51
  102. package/dist/hooks/useActorMutation.d.ts.map +0 -1
  103. package/dist/hooks/useActorMutation.js +0 -70
  104. package/dist/hooks/useActorMutation.js.map +0 -1
  105. package/dist/hooks/useActorQuery.d.ts +0 -45
  106. package/dist/hooks/useActorQuery.d.ts.map +0 -1
  107. package/dist/hooks/useActorQuery.js +0 -67
  108. package/dist/hooks/useActorQuery.js.map +0 -1
  109. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts +0 -51
  110. package/dist/hooks/useActorSuspenseInfiniteQuery.d.ts.map +0 -1
  111. package/dist/hooks/useActorSuspenseInfiniteQuery.js +0 -76
  112. package/dist/hooks/useActorSuspenseInfiniteQuery.js.map +0 -1
  113. package/dist/hooks/useActorSuspenseQuery.d.ts +0 -32
  114. package/dist/hooks/useActorSuspenseQuery.d.ts.map +0 -1
  115. package/dist/hooks/useActorSuspenseQuery.js +0 -58
  116. package/dist/hooks/useActorSuspenseQuery.js.map +0 -1
  117. package/dist/ownedAuthentication.d.ts +0 -52
  118. package/dist/ownedAuthentication.d.ts.map +0 -1
  119. package/dist/ownedAuthentication.js +0 -49
  120. package/dist/ownedAuthentication.js.map +0 -1
  121. package/dist/server.d.ts +0 -21
  122. package/dist/server.d.ts.map +0 -1
  123. package/dist/server.js +0 -23
  124. package/dist/server.js.map +0 -1
  125. package/dist/testing.d.ts +0 -19
  126. package/dist/testing.d.ts.map +0 -1
  127. package/dist/testing.js +0 -19
  128. package/dist/testing.js.map +0 -1
  129. package/dist/types.d.ts +0 -671
  130. package/dist/types.d.ts.map +0 -1
  131. package/dist/types.js +0 -5
  132. package/dist/types.js.map +0 -1
  133. package/dist/utils.d.ts +0 -207
  134. package/dist/utils.d.ts.map +0 -1
  135. package/dist/utils.js +0 -405
  136. package/dist/utils.js.map +0 -1
  137. package/dist/validation.d.ts +0 -136
  138. package/dist/validation.d.ts.map +0 -1
  139. package/dist/validation.js +0 -144
  140. package/dist/validation.js.map +0 -1
  141. package/src/auth/auth-client-compat.ts +0 -273
  142. package/src/auth/authentication-manager.ts +0 -1682
  143. package/src/auth/constants.ts +0 -32
  144. package/src/auth/createIdentityAttributeHooks.ts +0 -169
  145. package/src/auth/identity-attributes-manager.ts +0 -226
  146. package/src/auth/identity-attributes.ts +0 -345
  147. package/src/auth/index.ts +0 -7
  148. package/src/auth/local-ii-probe.ts +0 -173
  149. package/src/auth/types.ts +0 -243
  150. package/src/createActorHooks.ts +0 -208
  151. package/src/createInfiniteQuery.ts +0 -670
  152. package/src/createMutation.ts +0 -324
  153. package/src/createQuery.ts +0 -369
  154. package/src/createReactorProvider.ts +0 -365
  155. package/src/createSuspenseInfiniteQuery.ts +0 -651
  156. package/src/createSuspenseQuery.ts +0 -304
  157. package/src/defineDisplayReactor.ts +0 -62
  158. package/src/defineReactor.ts +0 -142
  159. package/src/defineReactorShared.ts +0 -268
  160. package/src/hooks/createAuthHooks.ts +0 -371
  161. package/src/hooks/index.ts +0 -103
  162. package/src/hooks/useActorInfiniteQuery.ts +0 -278
  163. package/src/hooks/useActorMethod.ts +0 -710
  164. package/src/hooks/useActorMutation.ts +0 -205
  165. package/src/hooks/useActorQuery.ts +0 -157
  166. package/src/hooks/useActorSuspenseInfiniteQuery.ts +0 -248
  167. package/src/hooks/useActorSuspenseQuery.ts +0 -147
  168. package/src/index.ts +0 -31
  169. package/src/ownedAuthentication.ts +0 -81
  170. package/src/server.ts +0 -23
  171. package/src/testing.ts +0 -18
  172. package/src/types.ts +0 -948
  173. package/src/utils.ts +0 -505
  174. package/src/validation.ts +0 -226
package/llms.txt CHANGED
@@ -1,280 +1,84 @@
1
1
  # @ic-reactor/react
2
2
 
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.
13
-
14
- ## Install
15
-
16
- ```bash
17
- pnpm add @ic-reactor/react @icp-sdk/core @tanstack/react-query
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])
135
- ```
136
-
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
- ```
154
-
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.
172
-
173
- ## Queries
174
-
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">`.
197
-
198
- ## Mutations, Invalidation and Retries
199
-
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`.
218
-
219
- ## Sign-In
220
-
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.
232
-
233
- ## Testing
234
-
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)`.
245
-
246
- ## Do Not
247
-
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`.
265
-
266
- ## Docs
267
-
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
3
+ > The React bindings of ic-reactor 4: `ReactorProvider`, `useClient` and
4
+ > `useAuth`. Their guide is the one `@ic-reactor/core` ships.
5
+
6
+ Applies to `@ic-reactor/react` 4.0.0-beta.2.
7
+
8
+ Read `node_modules/@ic-reactor/core/llms.txt`. It is the one guide for both
9
+ packages: setup, the provider and `useAuth`, reads, writes, errors, values,
10
+ and a complete React example, with the rules each call enforces.
11
+
12
+ This package has no hook around `useQuery` or `useMutation`: call TanStack
13
+ Query's own hooks with the options the client builds,
14
+ `useQuery(client.queryOptions(...))` and
15
+ `useMutation(client.mutationOptions(...))`. It never re-exports
16
+ `@ic-reactor/core`: import each name from the package that defines it.
17
+
18
+ ## Keys follow the caller a render shows
19
+
20
+ Call `useClient()` in the body of each component that builds keys or read
21
+ options, on every render, and build them from what it returns there, never
22
+ from a client held at module scope. Do not keep what it returns past the
23
+ render: while a page hydrates it is a view for the anonymous caller, and a view
24
+ never moves on. An effect or callback that uses it closes over its render's
25
+ value and lists it in its dependencies (`react-hooks/exhaustive-deps`), so it
26
+ runs again with the client after hydrating. Kept from the hydrating render
27
+ anywhere else (`useState(client)`, `useRef(client)`, a `useMemo` or
28
+ `useCallback` with `[]`, a module variable), it stays that view: the component
29
+ shows the anonymous caller's data, a read it starts while the user is signed
30
+ in is cancelled, and a write through it still signs as the live caller.
31
+ Nothing warns about it. A nested `ReactorProvider` given it holds the client
32
+ the view was made over. `useClient()` follows the client's caller: a
33
+ component that calls it renders again on each sign-in, switch of account and
34
+ sign-out, with the new caller's keys, and never for a change of status that
35
+ leaves the caller as it is. It returns the provider's client object itself,
36
+ except while a page hydrates in a browser that holds a session: on a server and
37
+ while a page hydrates the caller is anonymous, as for `useAuth()`, so the
38
+ hydrating render gets a view of the client whose `queryKey`, `queryOptions`,
39
+ `caller()` and `authState()` are the anonymous caller's. It finds what the
40
+ server prefetched and dehydrated, matches the server's HTML and sends nothing.
41
+ Canisters, writes, `signIn`, `signOut` and the `QueryClient` are the client's
42
+ own, and a write signs as the caller current when it runs. Right after
43
+ hydrating, React renders the component again with the session, and each read
44
+ loads the user's keys once: until the user's data arrives, a read shows its
45
+ loading state, and a `useSuspenseQuery` read its boundary's fallback. A tab
46
+ whose caller is anonymous anyway (no session,
47
+ or one that expired or is signed in elsewhere) renders no `useClient()`
48
+ component again, and a client built with `identity` is always returned as it
49
+ is.
50
+
51
+ A signed-in reload has three accepted costs:
52
+
53
+ - A Suspense boundary still dehydrated below a component that calls
54
+ `useClient()` or `useAuth()` (its lazy code still loading, or its streamed
55
+ HTML not arrived yet) is rendered on the client when that component
56
+ moves on to the session: it shows its fallback, not the server's HTML, and
57
+ React 18 reports a recoverable error. Its data is still the user's. A
58
+ `useAuth()` component also moves on in a tab whose session expired or is
59
+ signed in elsewhere. Render such a boundary where no component that renders with the caller sits above
60
+ it, or pass it in as `children`, which a component's own update does not
61
+ render again.
62
+ - A read the hydrating render built may still run once (a refetch of stale
63
+ data on mount, or a fetch from an effect) and is then cancelled before
64
+ anything is sent. A key that holds data keeps it as it was, and a fetch of
65
+ it resolves with that data. A key with none fails, with `kind`
66
+ `"cancelled"` and `code` `"caller_changed"`, until the anonymous caller is
67
+ current again: the hydrating render's own reads never show it, but a
68
+ listener of `client.queryClient.getQueryCache().subscribe()` (an
69
+ `"updated"` event whose `action.type` is `"error"`; the client takes no
70
+ `QueryCache` of yours), an effect that awaits the fetch and, for a
71
+ `useSuspenseQuery` read the server rendered without dehydrating its data,
72
+ React's `onRecoverableError` (as the reported error's `cause` on React 19)
73
+ see it, so ignore `kind` `"cancelled"` there.
74
+ - Put a Suspense boundary above every component that reads with
75
+ `useSuspenseQuery`, on React 18 and 19 alike. The move to the session is a
76
+ synchronous update that such a read suspends until the user's data arrives:
77
+ with a boundary above, it shows the fallback, then the user's data, and the
78
+ rest of the page responds meanwhile. With none, React 18 refuses the update
79
+ ("A component suspended while responding to synchronous input") and
80
+ unmounts the root, so a signed-in reload renders nothing. React 19 keeps the
81
+ server's HTML on screen, but until the user's data arrives, however long the
82
+ read and its retries take, a click or any other update outside a transition
83
+ commits nothing, and neither does a transition that renders the reading
84
+ component again.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ic-reactor/react",
3
- "version": "3.13.0",
4
- "description": "IC Reactor React library for building Internet Computer apps",
3
+ "version": "4.0.0-beta.2",
4
+ "description": "ic-reactor 4 React bindings: a provider that owns one client per tree, useClient and useAuth",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -13,15 +13,9 @@
13
13
  "exports": {
14
14
  ".": {
15
15
  "types": "./dist/index.d.ts",
16
- "react-server": "./dist/server.js",
17
16
  "import": "./dist/index.js",
18
17
  "default": "./dist/index.js"
19
18
  },
20
- "./testing": {
21
- "types": "./dist/testing.d.ts",
22
- "import": "./dist/testing.js",
23
- "default": "./dist/testing.js"
24
- },
25
19
  "./package.json": "./package.json"
26
20
  },
27
21
  "files": [
@@ -41,7 +35,7 @@
41
35
  "bugs": {
42
36
  "url": "https://github.com/B3Pay/ic-reactor/issues"
43
37
  },
44
- "homepage": "https://ic-reactor.b3pay.net/v3/packages/react",
38
+ "homepage": "https://ic-reactor.b3pay.net/v4/packages/react/",
45
39
  "keywords": [
46
40
  "internet-computer",
47
41
  "icp",
@@ -58,62 +52,40 @@
58
52
  ],
59
53
  "author": "Behrad Deylami",
60
54
  "license": "MIT",
61
- "dependencies": {
62
- "@ic-reactor/core": "^3.13.0"
63
- },
64
55
  "peerDependencies": {
65
- "@icp-sdk/auth": "^8.0.0 || ^10.0.0",
66
- "@icp-sdk/core": "^6.1.0",
67
- "@noble/curves": "^2.2.0",
68
56
  "@tanstack/react-query": "^5.90.2",
69
57
  "react": ">=18.0.0",
70
- "react-dom": ">=18.0.0"
58
+ "@ic-reactor/core": "4.0.0-beta.2"
71
59
  },
72
60
  "devDependencies": {
73
- "@icp-sdk/auth": "^10.0.0",
74
- "@icp-sdk/auth-v8": "npm:@icp-sdk/auth@^8.0.3",
75
- "@icp-sdk/core": "^6.1.0",
76
- "@noble/curves": "^2.2.0",
61
+ "@candid-core/schema": "0.3.0",
77
62
  "@size-limit/preset-small-lib": "^13.0.3",
78
63
  "@tanstack/react-query": "^5.102.8",
79
64
  "@testing-library/dom": "^10.4.1",
80
- "@testing-library/jest-dom": "^7.0.1",
81
65
  "@testing-library/react": "^16.3.3",
82
66
  "@types/react": "^19.3.0",
83
67
  "@types/react-dom": "^19.3.0",
84
- "fake-indexeddb": "^6.2.5",
85
68
  "jsdom": "^30.0.1",
86
69
  "react": "^19.3.0",
87
70
  "react-dom": "^19.3.0",
88
71
  "size-limit": "^13.0.3",
89
- "vitest": "^5.0.0"
72
+ "vitest": "^5.0.0",
73
+ "@ic-reactor/core": "4.0.0-beta.2"
90
74
  },
91
75
  "size-limit": [
92
76
  {
93
- "name": "React Library",
77
+ "name": "React bindings",
94
78
  "path": "dist/index.js",
95
- "limit": "30 KB",
79
+ "limit": "1.28 KB",
96
80
  "gzip": true,
97
81
  "ignore": [
98
82
  "react",
99
- "react-dom",
83
+ "react/jsx-runtime",
100
84
  "@ic-reactor/core",
101
- "@tanstack/react-query",
102
- "@icp-sdk/auth"
85
+ "@tanstack/react-query"
103
86
  ]
104
87
  }
105
88
  ],
106
- "peerDependenciesMeta": {
107
- "@icp-sdk/auth": {
108
- "optional": true
109
- },
110
- "@noble/curves": {
111
- "optional": true
112
- },
113
- "react-dom": {
114
- "optional": true
115
- }
116
- },
117
89
  "scripts": {
118
90
  "build": "rm -rf dist tsconfig.tsbuildinfo && tsc",
119
91
  "test": "vitest run",