@ic-reactor/react 3.1.1 → 3.1.3

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 (2) hide show
  1. package/README.md +86 -328
  2. package/package.json +6 -6
package/README.md CHANGED
@@ -1,386 +1,144 @@
1
1
  # @ic-reactor/react
2
2
 
3
- <div align="center">
4
- <strong>The Ultimate React Hooks for the Internet Computer.</strong>
5
- <br><br>
6
-
7
- [![npm version](https://img.shields.io/npm/v/@ic-reactor/react.svg)](https://www.npmjs.com/package/@ic-reactor/react)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
10
- </div>
3
+ React bindings for IC Reactor. This package re-exports everything from
4
+ `@ic-reactor/core` and adds hook factories, auth hooks, direct reactor hooks,
5
+ and reusable query or mutation factories built around TanStack Query.
11
6
 
12
- ---
13
-
14
- Connect your React application to the Internet Computer Blockchain with full [TanStack Query](https://tanstack.com/query) integration for caching, suspense, and infinite queries.
15
-
16
- ## Features
17
-
18
- - ⚛️ **TanStack Query Integration** — Full power of React Query (caching, refetching, suspense, infinite queries)
19
- - � **End-to-End Type Safety** — Automatic type inference from your Candid files
20
- - � **Auto Transformations** — `DisplayReactor` converts BigInt to string, Principal to text, and more
21
- - 📦 **Result Unwrapping** — Automatic `Ok`/`Err` handling from Candid Result types
22
- - 🔐 **Authentication** — Easy-to-use hooks with Internet Identity integration
23
- - 🏗️ **Multi-Actor Support** — Manage multiple canisters with shared authentication
24
-
25
- ## Installation
7
+ ## Install
26
8
 
27
9
  ```bash
28
- # With npm
29
- npm install @ic-reactor/react @tanstack/react-query @icp-sdk/core
10
+ pnpm add @ic-reactor/react @icp-sdk/core @tanstack/react-query
30
11
 
31
- # With pnpm
32
- pnpm add @ic-reactor/react @tanstack/react-query @icp-sdk/core
33
-
34
- # Optional: For Internet Identity authentication
35
- npm install @icp-sdk/auth
12
+ # Optional: Internet Identity login helpers
13
+ pnpm add @icp-sdk/auth
36
14
  ```
37
15
 
38
16
  ## Quick Start
39
17
 
40
- ### 1. Setup ClientManager and Reactor
41
-
42
- ```typescript
18
+ ```tsx
43
19
  // src/reactor.ts
44
- import { ClientManager, Reactor } from "@ic-reactor/react"
20
+ import { ClientManager, Reactor, createActorHooks } from "@ic-reactor/react"
45
21
  import { QueryClient } from "@tanstack/react-query"
46
- import { idlFactory, type _SERVICE } from "./declarations/my_canister"
22
+ import { idlFactory, type _SERVICE } from "./declarations/backend"
47
23
 
48
- // Create query client for caching
49
24
  export const queryClient = new QueryClient()
50
25
 
51
- // Create client manager (handles identity and agent)
52
- // Create client manager (handles identity and agent)
53
26
  export const clientManager = new ClientManager({
54
27
  queryClient,
55
- withCanisterEnv: true, // Reads canister IDs from environment/cookies
28
+ withCanisterEnv: true,
56
29
  })
57
30
 
58
- // Create reactor for your canister
59
31
  export const backend = new Reactor<_SERVICE>({
60
32
  clientManager,
61
33
  idlFactory,
62
- name: "backend", // Required: explicit name for the reactor
34
+ name: "backend",
63
35
  })
64
- ```
65
-
66
- ### 2. Create Hooks
67
-
68
- ```typescript
69
- // src/hooks.ts
70
- import { createActorHooks, createAuthHooks } from "@ic-reactor/react"
71
- import { backend, clientManager } from "./reactor"
72
-
73
- // Create actor hooks for queries and mutations
74
- export const { useActorQuery, useActorMutation, useActorSuspenseQuery } =
75
- createActorHooks(backend)
76
36
 
77
- // Create auth hooks for login/logout
78
- export const { useAuth, useUserPrincipal } = createAuthHooks(clientManager)
37
+ export const {
38
+ useActorQuery,
39
+ useActorMutation,
40
+ useActorSuspenseQuery,
41
+ useActorMethod,
42
+ } = createActorHooks(backend)
79
43
  ```
80
44
 
81
- ### 3. Setup Provider (not required) and Use in Components
82
-
83
45
  ```tsx
84
46
  // src/App.tsx
85
47
  import { QueryClientProvider } from "@tanstack/react-query"
86
- import { queryClient } from "./reactor"
87
- import { useAuth, useActorQuery, useActorMutation } from "./hooks"
88
-
89
- function App() {
90
- return (
91
- <QueryClientProvider client={queryClient}>
92
- <AuthButton />
93
- <Greeting />
94
- </QueryClientProvider>
95
- )
96
- }
97
-
98
- function AuthButton() {
99
- const { login, logout, isAuthenticated, principal } = useAuth()
100
-
101
- return isAuthenticated ? (
102
- <button onClick={() => logout()}>
103
- Logout {principal?.toText().slice(0, 8)}...
104
- </button>
105
- ) : (
106
- <button onClick={() => login()}>Login with Internet Identity</button>
107
- )
108
- }
48
+ import { queryClient, useActorMethod, useActorQuery } from "./reactor"
109
49
 
110
50
  function Greeting() {
111
- // Query: Fetch data (auto-cached!)
112
- const { data, isPending, error } = useActorQuery({
51
+ const { data, isPending } = useActorQuery({
113
52
  functionName: "greet",
114
53
  args: ["World"],
115
54
  })
116
55
 
117
- if (isPending) return <div>Loading...</div>
118
- if (error) return <div>Error: {error.message}</div>
119
-
120
- return <h1>{data}</h1>
56
+ if (isPending) return <p>Loading...</p>
57
+ return <p>{data}</p>
121
58
  }
122
- ```
123
-
124
- ## Core Concepts
125
59
 
126
- ### Reactor vs DisplayReactor
60
+ function Increment() {
61
+ const { call, isPending } = useActorMethod({ functionName: "increment" })
127
62
 
128
- | Feature | `Reactor` | `DisplayReactor` |
129
- | ------------- | ---------------- | ---------------------------- |
130
- | Types | Raw Candid types | Display-friendly types |
131
- | BigInt | `bigint` | `string` |
132
- | Principal | `Principal` | `string` |
133
- | Vec nat8 | `Uint8Array` | <= 512 bytes: `string` (hex) |
134
- | Result | Unwrapped | Unwrapped |
135
- | Form-friendly | No | Yes |
136
-
137
- ```typescript
138
- import { DisplayReactor } from "@ic-reactor/react"
139
-
140
- // DisplayReactor for form-friendly UI work
141
- const backend = new DisplayReactor<_SERVICE>({
142
- clientManager,
143
- idlFactory,
144
- canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
145
- })
146
-
147
- // Now hooks return strings instead of bigint/Principal
148
- const { data } = useActorQuery({
149
- functionName: "icrc1_balance_of",
150
- args: [{ owner: "aaaaa-aa", subaccount: [] }], // strings!
151
- })
152
- // data is "100000000" instead of 100000000n
153
- ```
154
-
155
- ## Hooks Reference
156
-
157
- ### Actor Hooks (from `createActorHooks`)
158
-
159
- | Hook | Description |
160
- | ------------------------------- | ---------------------------------------------- |
161
- | `useActorQuery` | Standard queries with loading states |
162
- | `useActorSuspenseQuery` | Suspense-enabled queries (data always defined) |
163
- | `useActorInfiniteQuery` | Paginated/infinite scroll queries |
164
- | `useActorSuspenseInfiniteQuery` | Suspense infinite queries |
165
- | `useActorMutation` | State-changing operations |
166
-
167
- ### Auth Hooks (from `createAuthHooks`)
168
-
169
- | Hook | Description |
170
- | ------------------ | ----------------------------------- |
171
- | `useAuth` | Login, logout, authentication state |
172
- | `useAgentState` | Agent initialization state |
173
- | `useUserPrincipal` | Current user's Principal |
174
-
175
- ## Query Examples
176
-
177
- ### Standard Query
178
-
179
- ```tsx
180
- const { data, isPending, error } = useActorQuery({
181
- functionName: "get_user",
182
- args: ["user-123"],
183
- staleTime: 5 * 60 * 1000, // 5 minutes
184
- })
185
- ```
186
-
187
- ### Suspense Query
188
-
189
- ```tsx
190
- // Parent must have <Suspense> boundary
191
- function UserProfile() {
192
- // data is never undefined with suspense!
193
- const { data } = useActorSuspenseQuery({
194
- functionName: "get_user",
195
- args: ["user-123"],
196
- })
63
+ return (
64
+ <button disabled={isPending} onClick={() => call([])}>
65
+ {isPending ? "Updating..." : "Increment"}
66
+ </button>
67
+ )
68
+ }
197
69
 
198
- return <div>{data.name}</div>
70
+ export function App() {
71
+ return (
72
+ <QueryClientProvider client={queryClient}>
73
+ <Greeting />
74
+ <Increment />
75
+ </QueryClientProvider>
76
+ )
199
77
  }
200
78
  ```
201
79
 
202
- ### Infinite Query
203
-
204
- ```tsx
205
- const { data, fetchNextPage, hasNextPage } = useActorInfiniteQuery({
206
- functionName: "get_posts",
207
- initialPageParam: 0,
208
- getNextPageParam: (lastPage, pages) => pages.length * 10,
209
- args: (pageParam) => [{ offset: pageParam, limit: 10 }],
80
+ ## Main APIs
81
+
82
+ - `createActorHooks(reactor)` for per-canister hooks like `useActorQuery` and
83
+ `useActorMutation`
84
+ - `createAuthHooks(clientManager)` for `useAuth`, `useAuthState`,
85
+ `useAgentState`, and `useUserPrincipal`
86
+ - direct reactor hooks like `useReactorQuery` when you want to pass the reactor
87
+ instance at call time
88
+ - factory helpers like `createQuery`, `createSuspenseQuery`,
89
+ `createInfiniteQuery`, `createSuspenseInfiniteQuery`, and `createMutation`
90
+ when the same operation must work both inside and outside React
91
+
92
+ ## Choosing the Right Pattern
93
+
94
+ - Use `createActorHooks` for the simplest component-first integration.
95
+ - Use query and mutation factories when you also need loader, action, service,
96
+ or test usage through `.fetch()`, `.execute()`, `.invalidate()`, or
97
+ `.getCacheData()`.
98
+ - Use `DisplayReactor` when you want UI-friendly values such as strings instead
99
+ of `bigint` or `Principal`.
100
+ - Use generated hooks from `@ic-reactor/vite-plugin` or `@ic-reactor/cli` when
101
+ you have larger canisters or frequent `.did` changes.
102
+
103
+ ## Factory Example
104
+
105
+ ```ts
106
+ import { createSuspenseQueryFactory, createMutation } from "@ic-reactor/react"
107
+ import { backend } from "./reactor"
108
+
109
+ export const getProfile = createSuspenseQueryFactory(backend, {
110
+ functionName: "get_profile",
210
111
  })
211
- ```
212
-
213
- `createInfiniteQuery(...)` and `createInfiniteQueryFactory(...)` support standard
214
- TanStack Query infinite-query options at the create level, including
215
- `refetchInterval`, `refetchOnMount`, `refetchOnWindowFocus`, `retry`, and `gcTime`.
216
112
 
217
- ## Mutation Examples
218
-
219
- ### Basic Mutation
220
-
221
- ```tsx
222
- const { mutate, isPending, error } = useActorMutation({
113
+ export const updateProfile = createMutation(backend, {
223
114
  functionName: "update_profile",
224
- onSuccess: (result) => {
225
- console.log("Profile updated!", result)
226
- },
227
- })
228
-
229
- // Call the mutation
230
- mutate([{ name: "Alice", bio: "Hello IC!" }])
231
- ```
232
-
233
- ## Query Factories
234
-
235
- Create reusable query configurations with factory functions:
236
-
237
- ```typescript
238
- import {
239
- createQuery,
240
- createSuspenseQuery,
241
- createMutation,
242
- } from "@ic-reactor/react"
243
-
244
- // Static query (no args at call time)
245
- export const tokenNameQuery = createSuspenseQuery(backend, {
246
- functionName: "icrc1_name",
247
- })
248
-
249
- // In component:
250
- const { data } = tokenNameQuery.useSuspenseQuery()
251
- ```
252
-
253
- ### Factory with Dynamic Args
254
-
255
- ```typescript
256
- import { createSuspenseQueryFactory } from "@ic-reactor/react"
257
-
258
- // Factory for balance queries
259
- export const getBalance = createSuspenseQueryFactory(backend, {
260
- functionName: "icrc1_balance_of",
261
- select: (balance) => `${balance} tokens`,
262
115
  })
263
-
264
- // In component - create the query instance with args at call time
265
- const balanceQuery = getBalance([{ owner: userPrincipal, subaccount: [] }])
266
- const { data } = balanceQuery.useSuspenseQuery()
267
116
  ```
268
117
 
269
- ### Infinite Query Factory (Route/Search Params Safe)
270
-
271
- Use `getKeyArgs` in the factory config to derive a stable logical identity from
272
- the first-page args, and keep pagination cursors inside `getArgs(pageParam)`.
273
- This prevents cache collisions when loaders rerun with different search params.
274
-
275
118
  ```tsx
276
- import { createInfiniteQueryFactory } from "@ic-reactor/react"
277
-
278
- type TodoSearch = {
279
- filter: "all" | "active" | "completed"
280
- q: string
281
- sort: "newest" | "oldest"
282
- }
283
-
284
- export const makeTodoListQuery = createInfiniteQueryFactory(todoReactor, {
285
- functionName: "list_todos",
286
- initialPageParam: 0,
287
- getKeyArgs: (args) => {
288
- const [request] = args
289
- return [
290
- {
291
- filter: request.filter,
292
- q: request.q,
293
- sort: request.sort,
294
- },
295
- ]
296
- },
297
- getNextPageParam: (lastPage) => lastPage.nextCursor,
298
- })
299
-
300
- // TanStack Router loader/search-param flow
301
- export async function loader({ context, deps }: any) {
302
- const search = deps.search as TodoSearch
303
-
304
- const todosQuery = makeTodoListQuery((cursor) => [
305
- {
306
- cursor,
307
- limit: 20,
308
- filter: search.filter,
309
- q: search.q,
310
- sort: search.sort,
311
- },
312
- ])
313
-
314
- await todosQuery.fetch()
315
- return { queryKey: todosQuery.getQueryKey() }
316
- }
317
-
318
- function TodosPage({ search }: { search: TodoSearch }) {
319
- const todosQuery = makeTodoListQuery((cursor) => [
320
- {
321
- cursor,
322
- limit: 20,
323
- filter: search.filter,
324
- q: search.q,
325
- sort: search.sort,
326
- },
327
- ])
328
-
329
- const { data, fetchNextPage, hasNextPage } = todosQuery.useInfiniteQuery()
330
- return null
331
- }
332
- ```
333
-
334
- ## Advanced: Direct Reactor Usage
335
-
336
- Access reactor methods directly for manual cache management:
119
+ const profileQuery = getProfile(["alice"])
120
+ const { data } = profileQuery.useSuspenseQuery()
337
121
 
338
- ```typescript
339
- // Fetch and cache
340
- await backend.fetchQuery({
341
- functionName: "get_user",
342
- args: ["user-123"],
343
- })
344
-
345
- // Get cached data (no fetch)
346
- const cached = backend.getQueryData({
347
- functionName: "get_user",
348
- args: ["user-123"],
349
- })
350
-
351
- // Invalidate cache to trigger refetch
352
- backend.invalidateQueries({
353
- functionName: "get_user",
354
- })
355
-
356
- // Direct call without caching
357
- const result = await backend.callMethod({
358
- functionName: "update_user",
359
- args: [{ name: "Alice" }],
122
+ const mutation = updateProfile.useMutation({
123
+ invalidateQueries: [profileQuery.getQueryKey()],
360
124
  })
361
125
  ```
362
126
 
363
127
  ## Re-exports
364
128
 
365
- `@ic-reactor/react` re-exports everything from `@ic-reactor/core`, so you typically only need one import:
366
-
367
- ```typescript
368
- // Everything from one package
369
- import {
370
- ClientManager,
371
- Reactor,
372
- DisplayReactor,
373
- createActorHooks,
374
- createAuthHooks,
375
- createQuery,
376
- CanisterError,
377
- } from "@ic-reactor/react"
378
- ```
379
-
380
- ## Documentation
129
+ `@ic-reactor/react` re-exports the core runtime, so you can import these from a
130
+ single package:
381
131
 
382
- For comprehensive guides and API reference, visit the [documentation site](https://ic-reactor.b3pay.net/v3).
132
+ - `ClientManager`
133
+ - `Reactor`
134
+ - `DisplayReactor`
135
+ - `CallError`
136
+ - `CanisterError`
137
+ - `ValidationError`
383
138
 
384
- ## License
139
+ ## See Also
385
140
 
386
- MIT © [Behrad Deylami](https://github.com/b3hr4d)
141
+ - Docs: https://ic-reactor.b3pay.net/v3/packages/react
142
+ - `@ic-reactor/core`: ../core/README.md
143
+ - `@ic-reactor/vite-plugin`: ../vite-plugin/README.md
144
+ - `@ic-reactor/cli`: ../cli/README.md
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ic-reactor/react",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "IC Reactor React Library",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -42,7 +42,7 @@
42
42
  "author": "Behrad Deylami",
43
43
  "license": "MIT",
44
44
  "dependencies": {
45
- "@ic-reactor/core": "^3.1.1"
45
+ "@ic-reactor/core": "^3.1.3"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "react": ">=18.0.0",
@@ -52,7 +52,7 @@
52
52
  "devDependencies": {
53
53
  "@icp-sdk/auth": "^5.0.0",
54
54
  "@icp-sdk/core": "^5.0.0",
55
- "@size-limit/preset-small-lib": "^12.0.0",
55
+ "@size-limit/preset-small-lib": "^12.0.1",
56
56
  "@tanstack/react-query": "^5.90",
57
57
  "@testing-library/dom": "^10.4.1",
58
58
  "@testing-library/jest-dom": "^6.9.1",
@@ -60,11 +60,11 @@
60
60
  "@types/react": "^19.2.14",
61
61
  "@types/react-dom": "^19.2.3",
62
62
  "fake-indexeddb": "^6.2.5",
63
- "jsdom": "^28.1.0",
63
+ "jsdom": "^29.0.0",
64
64
  "react": "^19.2.3",
65
65
  "react-dom": "^19.2.3",
66
- "size-limit": "^12.0.0",
67
- "vitest": "^4.0.18"
66
+ "size-limit": "^12.0.1",
67
+ "vitest": "^4.1.0"
68
68
  },
69
69
  "size-limit": [
70
70
  {