@ic-reactor/react 3.0.0-beta.1 → 3.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.
- package/README.md +256 -86
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,147 +1,317 @@
|
|
|
1
1
|
# @ic-reactor/react
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
<div align="center">
|
|
4
|
+
<strong>The Ultimate React Hooks for the Internet Computer.</strong>
|
|
5
|
+
<br><br>
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@ic-reactor/react)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
</div>
|
|
11
|
+
|
|
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.
|
|
5
15
|
|
|
6
16
|
## Features
|
|
7
17
|
|
|
8
|
-
- ⚛️ **
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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
|
|
12
24
|
|
|
13
25
|
## Installation
|
|
14
26
|
|
|
15
27
|
```bash
|
|
16
|
-
|
|
28
|
+
# With npm
|
|
29
|
+
npm install @ic-reactor/react @tanstack/react-query @icp-sdk/core
|
|
30
|
+
|
|
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
|
|
17
36
|
```
|
|
18
37
|
|
|
19
38
|
## Quick Start
|
|
20
39
|
|
|
21
|
-
### 1.
|
|
22
|
-
|
|
23
|
-
Create your actor hooks using `createActorHooks`. This is the "One Stop Shop" for interacting with your canister.
|
|
40
|
+
### 1. Setup ClientManager and Reactor
|
|
24
41
|
|
|
25
42
|
```typescript
|
|
26
|
-
// src/
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
// src/reactor.ts
|
|
44
|
+
import { ClientManager, Reactor } from "@ic-reactor/react"
|
|
45
|
+
import { QueryClient } from "@tanstack/react-query"
|
|
46
|
+
import { idlFactory, type _SERVICE } from "./declarations/my_canister"
|
|
47
|
+
|
|
48
|
+
// Create query client for caching
|
|
49
|
+
export const queryClient = new QueryClient()
|
|
50
|
+
|
|
51
|
+
// Create client manager (handles identity and agent)
|
|
52
|
+
export const clientManager = new ClientManager({
|
|
53
|
+
queryClient,
|
|
54
|
+
withProcessEnv: true, // Reads DFX_NETWORK from environment
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Create reactor for your canister
|
|
58
|
+
export const backend = new Reactor<_SERVICE>({
|
|
59
|
+
clientManager,
|
|
60
|
+
idlFactory,
|
|
61
|
+
canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
|
|
62
|
+
})
|
|
35
63
|
```
|
|
36
64
|
|
|
37
|
-
### 2.
|
|
65
|
+
### 2. Create Hooks
|
|
38
66
|
|
|
39
|
-
|
|
67
|
+
```typescript
|
|
68
|
+
// src/hooks.ts
|
|
69
|
+
import { createActorHooks, createAuthHooks } from "@ic-reactor/react"
|
|
70
|
+
import { backend, clientManager } from "./reactor"
|
|
71
|
+
|
|
72
|
+
// Create actor hooks for queries and mutations
|
|
73
|
+
export const { useActorQuery, useActorMutation, useActorSuspenseQuery } =
|
|
74
|
+
createActorHooks(backend)
|
|
75
|
+
|
|
76
|
+
// Create auth hooks for login/logout
|
|
77
|
+
export const { useAuth, useUserPrincipal } = createAuthHooks(clientManager)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. Setup Provider (not required) and Use in Components
|
|
40
81
|
|
|
41
82
|
```tsx
|
|
42
83
|
// src/App.tsx
|
|
43
|
-
import {
|
|
84
|
+
import { QueryClientProvider } from "@tanstack/react-query"
|
|
85
|
+
import { queryClient } from "./reactor"
|
|
86
|
+
import { useAuth, useActorQuery, useActorMutation } from "./hooks"
|
|
44
87
|
|
|
45
88
|
function App() {
|
|
46
|
-
// useAuth() auto-initializes - no separate setup needed!
|
|
47
|
-
const { isAuthenticated, login, logout } = useAuth()
|
|
48
|
-
|
|
49
89
|
return (
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
<button onClick={login}>Login with II</button>
|
|
55
|
-
)}
|
|
56
|
-
<Dashboard />
|
|
57
|
-
</div>
|
|
90
|
+
<QueryClientProvider client={queryClient}>
|
|
91
|
+
<AuthButton />
|
|
92
|
+
<Greeting />
|
|
93
|
+
</QueryClientProvider>
|
|
58
94
|
)
|
|
59
95
|
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### 3. Use in Components
|
|
63
96
|
|
|
64
|
-
|
|
97
|
+
function AuthButton() {
|
|
98
|
+
const { login, logout, isAuthenticated, principal } = useAuth()
|
|
65
99
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
100
|
+
return isAuthenticated ? (
|
|
101
|
+
<button onClick={() => logout()}>
|
|
102
|
+
Logout {principal?.toText().slice(0, 8)}...
|
|
103
|
+
</button>
|
|
104
|
+
) : (
|
|
105
|
+
<button onClick={() => login()}>Login with Internet Identity</button>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
69
108
|
|
|
70
|
-
function
|
|
109
|
+
function Greeting() {
|
|
71
110
|
// Query: Fetch data (auto-cached!)
|
|
72
|
-
const { data
|
|
73
|
-
functionName: "
|
|
74
|
-
args: [
|
|
111
|
+
const { data, isPending, error } = useActorQuery({
|
|
112
|
+
functionName: "greet",
|
|
113
|
+
args: ["World"],
|
|
75
114
|
})
|
|
76
115
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
functionName: "icrc1_transfer",
|
|
80
|
-
onSuccess: () => {
|
|
81
|
-
console.log("Transfer successful!")
|
|
82
|
-
}
|
|
83
|
-
})
|
|
116
|
+
if (isPending) return <div>Loading...</div>
|
|
117
|
+
if (error) return <div>Error: {error.message}</div>
|
|
84
118
|
|
|
85
|
-
return
|
|
86
|
-
<div>
|
|
87
|
-
<h1>Balance: {balance?.toString()}</h1>
|
|
88
|
-
<button onClick={() => transfer(...)} disabled={isPending}>
|
|
89
|
-
Transfer
|
|
90
|
-
</button>
|
|
91
|
-
</div>
|
|
92
|
-
)
|
|
119
|
+
return <h1>{data}</h1>
|
|
93
120
|
}
|
|
94
121
|
```
|
|
95
122
|
|
|
96
|
-
|
|
123
|
+
## Core Concepts
|
|
97
124
|
|
|
98
|
-
|
|
125
|
+
### Reactor vs DisplayReactor
|
|
126
|
+
|
|
127
|
+
| Feature | `Reactor` | `DisplayReactor` |
|
|
128
|
+
| ------------- | ---------------- | --------------------------- |
|
|
129
|
+
| Types | Raw Candid types | Display-friendly types |
|
|
130
|
+
| BigInt | `bigint` | `string` |
|
|
131
|
+
| Principal | `Principal` | `string` |
|
|
132
|
+
| Vec nat8 | `Uint8Array` | <= 96 bytes: `string` (hex) |
|
|
133
|
+
| Result | Unwrapped | Unwrapped |
|
|
134
|
+
| Form-friendly | No | Yes |
|
|
99
135
|
|
|
100
136
|
```typescript
|
|
101
|
-
|
|
102
|
-
|
|
137
|
+
import { DisplayReactor } from "@ic-reactor/react"
|
|
138
|
+
|
|
139
|
+
// DisplayReactor for form-friendly UI work
|
|
140
|
+
const backend = new DisplayReactor<_SERVICE>({
|
|
141
|
+
clientManager,
|
|
103
142
|
idlFactory,
|
|
104
|
-
|
|
143
|
+
canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
|
|
105
144
|
})
|
|
145
|
+
|
|
146
|
+
// Now hooks return strings instead of bigint/Principal
|
|
147
|
+
const { data } = useActorQuery({
|
|
148
|
+
functionName: "icrc1_balance_of",
|
|
149
|
+
args: [{ owner: "aaaaa-aa", subaccount: [] }], // strings!
|
|
150
|
+
})
|
|
151
|
+
// data is "100000000" instead of 100000000n
|
|
106
152
|
```
|
|
107
153
|
|
|
108
|
-
|
|
154
|
+
## Hooks Reference
|
|
109
155
|
|
|
110
|
-
|
|
156
|
+
### Actor Hooks (from `createActorHooks`)
|
|
111
157
|
|
|
112
|
-
|
|
113
|
-
|
|
158
|
+
| Hook | Description |
|
|
159
|
+
| ------------------------------- | ---------------------------------------------- |
|
|
160
|
+
| `useActorQuery` | Standard queries with loading states |
|
|
161
|
+
| `useActorSuspenseQuery` | Suspense-enabled queries (data always defined) |
|
|
162
|
+
| `useActorInfiniteQuery` | Paginated/infinite scroll queries |
|
|
163
|
+
| `useActorSuspenseInfiniteQuery` | Suspense infinite queries |
|
|
164
|
+
| `useActorMutation` | State-changing operations |
|
|
114
165
|
|
|
115
|
-
|
|
116
|
-
const { login, logout, identity, isAuthenticated } = useAuth()
|
|
166
|
+
### Auth Hooks (from `createAuthHooks`)
|
|
117
167
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
168
|
+
| Hook | Description |
|
|
169
|
+
| ------------------ | ----------------------------------- |
|
|
170
|
+
| `useAuth` | Login, logout, authentication state |
|
|
171
|
+
| `useAgentState` | Agent initialization state |
|
|
172
|
+
| `useUserPrincipal` | Current user's Principal |
|
|
173
|
+
|
|
174
|
+
## Query Examples
|
|
175
|
+
|
|
176
|
+
### Standard Query
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
const { data, isPending, error } = useActorQuery({
|
|
180
|
+
functionName: "get_user",
|
|
181
|
+
args: ["user-123"],
|
|
182
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
183
|
+
})
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Suspense Query
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
// Parent must have <Suspense> boundary
|
|
190
|
+
function UserProfile() {
|
|
191
|
+
// data is never undefined with suspense!
|
|
192
|
+
const { data } = useActorSuspenseQuery({
|
|
193
|
+
functionName: "get_user",
|
|
194
|
+
args: ["user-123"],
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
return <div>{data.name}</div>
|
|
127
198
|
}
|
|
128
199
|
```
|
|
129
200
|
|
|
130
|
-
###
|
|
201
|
+
### Infinite Query
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
const { data, fetchNextPage, hasNextPage } = useActorInfiniteQuery({
|
|
205
|
+
functionName: "get_posts",
|
|
206
|
+
initialPageParam: 0,
|
|
207
|
+
getNextPageParam: (lastPage, pages) => pages.length * 10,
|
|
208
|
+
args: (pageParam) => [{ offset: pageParam, limit: 10 }],
|
|
209
|
+
})
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Mutation Examples
|
|
213
|
+
|
|
214
|
+
### Basic Mutation
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
const { mutate, isPending, error } = useActorMutation({
|
|
218
|
+
functionName: "update_profile",
|
|
219
|
+
onSuccess: (result) => {
|
|
220
|
+
console.log("Profile updated!", result)
|
|
221
|
+
},
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// Call the mutation
|
|
225
|
+
mutate([{ name: "Alice", bio: "Hello IC!" }])
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Query Factories
|
|
229
|
+
|
|
230
|
+
Create reusable query configurations with factory functions:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import {
|
|
234
|
+
createQuery,
|
|
235
|
+
createSuspenseQuery,
|
|
236
|
+
createMutation,
|
|
237
|
+
} from "@ic-reactor/react"
|
|
238
|
+
|
|
239
|
+
// Static query (no args at call time)
|
|
240
|
+
export const tokenNameQuery = createSuspenseQuery(backend, {
|
|
241
|
+
functionName: "icrc1_name",
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// In component:
|
|
245
|
+
const { data } = tokenNameQuery.useSuspenseQuery()
|
|
246
|
+
```
|
|
131
247
|
|
|
132
|
-
|
|
248
|
+
### Factory with Dynamic Args
|
|
133
249
|
|
|
134
250
|
```typescript
|
|
135
|
-
import {
|
|
251
|
+
import { createSuspenseQueryFactory } from "@ic-reactor/react"
|
|
136
252
|
|
|
137
|
-
|
|
253
|
+
// Factory for balance queries
|
|
254
|
+
export const getBalance = createSuspenseQueryFactory(backend, {
|
|
138
255
|
functionName: "icrc1_balance_of",
|
|
139
|
-
select: (balance) => balance
|
|
256
|
+
select: (balance) => `${balance} tokens`,
|
|
140
257
|
})
|
|
141
258
|
|
|
142
|
-
|
|
259
|
+
// In component - pass args at call time
|
|
260
|
+
const { data } = getBalance.useSuspenseQuery({
|
|
261
|
+
args: [{ owner: userPrincipal, subaccount: [] }],
|
|
262
|
+
})
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Advanced: Direct Reactor Usage
|
|
266
|
+
|
|
267
|
+
Access reactor methods directly for manual cache management:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// Fetch and cache
|
|
271
|
+
await backend.fetchQuery({
|
|
272
|
+
functionName: "get_user",
|
|
273
|
+
args: ["user-123"],
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
// Get cached data (no fetch)
|
|
277
|
+
const cached = backend.getQueryData({
|
|
278
|
+
functionName: "get_user",
|
|
279
|
+
args: ["user-123"],
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
// Invalidate cache to trigger refetch
|
|
283
|
+
backend.invalidateQueries({
|
|
284
|
+
functionName: "get_user",
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
// Direct call without caching
|
|
288
|
+
const result = await backend.callMethod({
|
|
289
|
+
functionName: "update_user",
|
|
290
|
+
args: [{ name: "Alice" }],
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Re-exports
|
|
295
|
+
|
|
296
|
+
`@ic-reactor/react` re-exports everything from `@ic-reactor/core`, so you typically only need one import:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// Everything from one package
|
|
300
|
+
import {
|
|
301
|
+
ClientManager,
|
|
302
|
+
Reactor,
|
|
303
|
+
DisplayReactor,
|
|
304
|
+
createActorHooks,
|
|
305
|
+
createAuthHooks,
|
|
306
|
+
createQuery,
|
|
307
|
+
CanisterError,
|
|
308
|
+
} from "@ic-reactor/react"
|
|
143
309
|
```
|
|
144
310
|
|
|
311
|
+
## Documentation
|
|
312
|
+
|
|
313
|
+
For comprehensive guides and API reference, visit the [documentation site](https://b3pay.github.io/ic-reactor/v3).
|
|
314
|
+
|
|
145
315
|
## License
|
|
146
316
|
|
|
147
|
-
MIT
|
|
317
|
+
MIT © [Behrad Deylami](https://github.com/b3hr4d)
|