@ahoo-wang/fetcher-react 3.9.2 → 3.9.5
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 +365 -0
- package/README.zh-CN.md +365 -0
- package/dist/api/apiHooks.d.ts +78 -0
- package/dist/api/apiHooks.d.ts.map +1 -0
- package/dist/api/createExecuteApiHooks.d.ts +105 -0
- package/dist/api/createExecuteApiHooks.d.ts.map +1 -0
- package/dist/api/createQueryApiHooks.d.ts +97 -0
- package/dist/api/createQueryApiHooks.d.ts.map +1 -0
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/core/useExecutePromise.d.ts +1 -1
- package/dist/core/useExecutePromise.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +714 -630
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ robust data fetching capabilities.
|
|
|
21
21
|
- ⚡ **Performance**: Optimized with useMemo, useCallback, and smart dependency management
|
|
22
22
|
- 🎯 **Options Flexibility**: Support for both static options and dynamic option suppliers
|
|
23
23
|
- 🔧 **Developer Experience**: Built-in loading states, error handling, and automatic re-rendering
|
|
24
|
+
- 🏗️ **API Hooks Generation**: Automatic type-safe React hooks generation from API objects
|
|
24
25
|
- 📊 **Advanced Query Hooks**: Specialized hooks for list, paged, single, count, and stream queries with state management
|
|
25
26
|
|
|
26
27
|
## Table of Contents
|
|
@@ -28,6 +29,7 @@ robust data fetching capabilities.
|
|
|
28
29
|
- [Installation](#installation)
|
|
29
30
|
- [Quick Start](#quick-start)
|
|
30
31
|
- [Usage](#usage)
|
|
32
|
+
- [API Hooks](#api-hooks)
|
|
31
33
|
- [Core Hooks](#core-hooks)
|
|
32
34
|
- [useExecutePromise](#useexecutepromise-hook)
|
|
33
35
|
- [usePromiseState](#usepromisestate-hook)
|
|
@@ -111,6 +113,369 @@ function App() {
|
|
|
111
113
|
|
|
112
114
|
## Usage
|
|
113
115
|
|
|
116
|
+
### API Hooks
|
|
117
|
+
|
|
118
|
+
#### createExecuteApiHooks
|
|
119
|
+
|
|
120
|
+
🚀 **Automatic Type-Safe API Hooks Generation** - Generate fully typed React hooks from API objects with automatic method discovery, class method support, and advanced execution control.
|
|
121
|
+
|
|
122
|
+
The `createExecuteApiHooks` function automatically discovers all function methods from an API object (including prototype chains for class instances) and creates corresponding React hooks with the naming pattern `use{CapitalizedMethodName}`. Each generated hook provides full state management, error handling, and supports custom execution callbacks with type-safe parameter access.
|
|
123
|
+
|
|
124
|
+
**Key Features:**
|
|
125
|
+
|
|
126
|
+
- **Automatic Method Discovery**: Traverses object properties and prototype chains
|
|
127
|
+
- **Type-Safe Hook Generation**: Full TypeScript inference for parameters and return types
|
|
128
|
+
- **Class Method Support**: Handles both static methods and class instances with `this` binding
|
|
129
|
+
- **Execution Control**: `onBeforeExecute` callback for parameter inspection/modification and abort controller access
|
|
130
|
+
- **Custom Error Types**: Support for specifying error types beyond the default `FetcherError`
|
|
131
|
+
|
|
132
|
+
```typescript jsx
|
|
133
|
+
import { createExecuteApiHooks } from '@ahoo-wang/fetcher-react';
|
|
134
|
+
import { api, get, post, patch, path, body, autoGeneratedError } from '@ahoo-wang/fetcher-decorator';
|
|
135
|
+
|
|
136
|
+
// Define your API service using decorators
|
|
137
|
+
import { api, get, post, patch, path, body, autoGeneratedError } from '@ahoo-wang/fetcher-decorator';
|
|
138
|
+
|
|
139
|
+
@api('/users')
|
|
140
|
+
class UserApi {
|
|
141
|
+
@get('/{id}')
|
|
142
|
+
getUser(@path('id') id: string): Promise<User> {
|
|
143
|
+
throw autoGeneratedError(id);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@post('')
|
|
147
|
+
createUser(@body() data: { name: string; email: string }): Promise<User> {
|
|
148
|
+
throw autoGeneratedError(data);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@patch('/{id}')
|
|
152
|
+
updateUser(@path('id') id: string, @body() updates: Partial<User>): Promise<User> {
|
|
153
|
+
throw autoGeneratedError(id, updates);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const userApi = new UserApi();
|
|
158
|
+
|
|
159
|
+
// Generate type-safe hooks
|
|
160
|
+
const apiHooks = createExecuteApiHooks({ api: userApi });
|
|
161
|
+
|
|
162
|
+
function UserComponent() {
|
|
163
|
+
// Hooks are automatically generated with proper typing
|
|
164
|
+
const { loading: getLoading, result: user, error: getError, execute: getUser } = apiHooks.useGetUser();
|
|
165
|
+
const { loading: createLoading, result: createdUser, error: createError, execute: createUser } = apiHooks.useCreateUser({
|
|
166
|
+
onBeforeExecute: (abortController, args) => {
|
|
167
|
+
// args is fully typed as [data: { name: string; email: string }]
|
|
168
|
+
const [data] = args;
|
|
169
|
+
// Modify parameters in place if needed
|
|
170
|
+
data.email = data.email.toLowerCase();
|
|
171
|
+
// Access abort controller for custom cancellation
|
|
172
|
+
abortController.signal.addEventListener('abort', () => {
|
|
173
|
+
console.log('User creation cancelled');
|
|
174
|
+
});
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const handleFetchUser = (userId: string) => {
|
|
179
|
+
getUser(userId); // Fully typed - only accepts string parameter
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const handleCreateUser = (userData: { name: string; email: string }) => {
|
|
183
|
+
createUser(userData); // Fully typed - only accepts correct data shape
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<div>
|
|
188
|
+
<button onClick={() => handleFetchUser('123')}>
|
|
189
|
+
Fetch User
|
|
190
|
+
</button>
|
|
191
|
+
{getLoading && <div>Loading user...</div>}
|
|
192
|
+
{getError && <div>Error: {getError.message}</div>}
|
|
193
|
+
{user && <div>User: {user.name}</div>}
|
|
194
|
+
|
|
195
|
+
<button onClick={() => handleCreateUser({ name: 'John', email: 'john@example.com' })}>
|
|
196
|
+
Create User
|
|
197
|
+
</button>
|
|
198
|
+
{createLoading && <div>Creating user...</div>}
|
|
199
|
+
{createError && <div>Error: {createError.message}</div>}
|
|
200
|
+
{createdUser && <div>Created: {createdUser.name}</div>}
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Custom Error Types:**
|
|
207
|
+
|
|
208
|
+
```typescript jsx
|
|
209
|
+
import { createExecuteApiHooks } from '@ahoo-wang/fetcher-react';
|
|
210
|
+
|
|
211
|
+
// Define custom error type
|
|
212
|
+
class ApiError extends Error {
|
|
213
|
+
constructor(
|
|
214
|
+
public statusCode: number,
|
|
215
|
+
message: string,
|
|
216
|
+
) {
|
|
217
|
+
super(message);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Generate hooks with custom error type
|
|
222
|
+
@api('/data')
|
|
223
|
+
class DataApi {
|
|
224
|
+
@get('/{id}')
|
|
225
|
+
getData(@path('id') id: string): Promise<Data> {
|
|
226
|
+
throw autoGeneratedError(id);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const apiHooks = createExecuteApiHooks<
|
|
231
|
+
{ getData: (id: string) => Promise<Data> },
|
|
232
|
+
ApiError
|
|
233
|
+
>({
|
|
234
|
+
api: new DataApi(),
|
|
235
|
+
errorType: ApiError,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
function MyComponent() {
|
|
239
|
+
const { error, execute } = apiHooks.useGetData();
|
|
240
|
+
|
|
241
|
+
// error is now typed as ApiError | undefined
|
|
242
|
+
if (error) {
|
|
243
|
+
console.log('Status code:', error.statusCode); // TypeScript knows about statusCode
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Advanced Usage with Class Methods:**
|
|
249
|
+
|
|
250
|
+
```typescript jsx
|
|
251
|
+
import { createExecuteApiHooks } from '@ahoo-wang/fetcher-react';
|
|
252
|
+
|
|
253
|
+
class ApiClient {
|
|
254
|
+
private baseUrl: string;
|
|
255
|
+
|
|
256
|
+
constructor(baseUrl: string) {
|
|
257
|
+
this.baseUrl = baseUrl;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async get(endpoint: string): Promise<any> {
|
|
261
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`);
|
|
262
|
+
return response.json();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async post(endpoint: string, data: any): Promise<any> {
|
|
266
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
267
|
+
method: 'POST',
|
|
268
|
+
headers: { 'Content-Type': 'application/json' },
|
|
269
|
+
body: JSON.stringify(data),
|
|
270
|
+
});
|
|
271
|
+
return response.json();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Static method example
|
|
275
|
+
static async healthCheck(): Promise<{ status: string }> {
|
|
276
|
+
const response = await fetch('/api/health');
|
|
277
|
+
return response.json();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const apiClient = new ApiClient('/api');
|
|
282
|
+
const apiHooks = createExecuteApiHooks({ api: apiClient });
|
|
283
|
+
|
|
284
|
+
// Generated hooks: useGet, usePost
|
|
285
|
+
// Static methods are also discovered: useHealthCheck
|
|
286
|
+
|
|
287
|
+
function ApiComponent() {
|
|
288
|
+
const { execute: getData } = apiHooks.useGet();
|
|
289
|
+
const { execute: postData } = apiHooks.usePost();
|
|
290
|
+
const { execute: healthCheck } = apiHooks.useHealthCheck();
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div>
|
|
294
|
+
<button onClick={() => getData('/users')}>Get Users</button>
|
|
295
|
+
<button onClick={() => postData('/users', { name: 'New User' })}>Create User</button>
|
|
296
|
+
<button onClick={() => healthCheck()}>Health Check</button>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
#### createQueryApiHooks
|
|
303
|
+
|
|
304
|
+
🚀 **Automatic Type-Safe Query API Hooks Generation** - Generate fully typed React query hooks from API objects with automatic query state management, auto-execution, and advanced execution control.
|
|
305
|
+
|
|
306
|
+
The `createQueryApiHooks` function automatically discovers query methods from an API object and creates corresponding React hooks that extend `useQuery`. Each generated hook provides automatic query parameter management, state management, and supports custom execution callbacks with type-safe query access.
|
|
307
|
+
|
|
308
|
+
**Key Features:**
|
|
309
|
+
|
|
310
|
+
- **Automatic Method Discovery**: Traverses object properties and prototype chains
|
|
311
|
+
- **Type-Safe Query Hooks**: Full TypeScript inference for query parameters and return types
|
|
312
|
+
- **Query State Management**: Built-in `setQuery` and `getQuery` for parameter management
|
|
313
|
+
- **Auto-Execution**: Optional automatic execution when query parameters change
|
|
314
|
+
- **Execution Control**: `onBeforeExecute` callback for query inspection/modification and abort controller access
|
|
315
|
+
- **Custom Error Types**: Support for specifying error types beyond the default `FetcherError`
|
|
316
|
+
|
|
317
|
+
```typescript jsx
|
|
318
|
+
import { createQueryApiHooks } from '@ahoo-wang/fetcher-react';
|
|
319
|
+
import { api, get, post, patch, path, body, autoGeneratedError } from '@ahoo-wang/fetcher-decorator';
|
|
320
|
+
|
|
321
|
+
// Define your API service using decorators
|
|
322
|
+
@api('/users')
|
|
323
|
+
class UserApi {
|
|
324
|
+
@get('')
|
|
325
|
+
getUsers(query: UserListQuery, attributes?: Record<string, any>): Promise<User[]> {
|
|
326
|
+
throw autoGeneratedError(query, attributes);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
@get('/{id}')
|
|
330
|
+
getUser(query: { id: string }, attributes?: Record<string, any>): Promise<User> {
|
|
331
|
+
throw autoGeneratedError(query, attributes);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
@post('')
|
|
335
|
+
createUser(query: { name: string; email: string }, attributes?: Record<string, any>): Promise<User> {
|
|
336
|
+
throw autoGeneratedError(query, attributes);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const apiHooks = createQueryApiHooks({ api: new UserApi() });
|
|
341
|
+
|
|
342
|
+
function UserListComponent() {
|
|
343
|
+
const { loading, result, error, execute, setQuery, getQuery } = apiHooks.useGetUsers({
|
|
344
|
+
initialQuery: { page: 1, limit: 10 },
|
|
345
|
+
autoExecute: true,
|
|
346
|
+
onBeforeExecute: (abortController, query) => {
|
|
347
|
+
// query is fully typed as UserListQuery
|
|
348
|
+
console.log('Executing query:', query);
|
|
349
|
+
// Modify query parameters in place if needed
|
|
350
|
+
query.page = Math.max(1, query.page);
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const handlePageChange = (page: number) => {
|
|
355
|
+
// Automatically updates query and triggers execution (if autoExecute: true)
|
|
356
|
+
setQuery({ ...getQuery(), page });
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
if (loading) return <div>Loading...</div>;
|
|
360
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<div>
|
|
364
|
+
<button onClick={() => handlePageChange(2)}>Go to page 2</button>
|
|
365
|
+
{result?.map(user => (
|
|
366
|
+
<div key={user.id}>{user.name}</div>
|
|
367
|
+
))}
|
|
368
|
+
</div>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function UserDetailComponent() {
|
|
373
|
+
const { result: user, execute } = apiHooks.useGetUser({
|
|
374
|
+
initialQuery: { id: '123' },
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<div>
|
|
379
|
+
<button onClick={execute}>Load User</button>
|
|
380
|
+
{user && <div>User: {user.name}</div>}
|
|
381
|
+
</div>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Custom Error Types:**
|
|
387
|
+
|
|
388
|
+
```typescript jsx
|
|
389
|
+
import { createQueryApiHooks } from '@ahoo-wang/fetcher-react';
|
|
390
|
+
|
|
391
|
+
// Define custom error type
|
|
392
|
+
class ApiError extends Error {
|
|
393
|
+
constructor(
|
|
394
|
+
public statusCode: number,
|
|
395
|
+
message: string,
|
|
396
|
+
) {
|
|
397
|
+
super(message);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Generate query hooks with custom error type
|
|
402
|
+
@api('/data')
|
|
403
|
+
class DataApi {
|
|
404
|
+
@get('/{id}')
|
|
405
|
+
getData(
|
|
406
|
+
query: { id: string },
|
|
407
|
+
attributes?: Record<string, any>,
|
|
408
|
+
): Promise<Data> {
|
|
409
|
+
throw autoGeneratedError(query, attributes);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const apiHooks = createQueryApiHooks<
|
|
414
|
+
{
|
|
415
|
+
getData: (
|
|
416
|
+
query: { id: string },
|
|
417
|
+
attributes?: Record<string, any>,
|
|
418
|
+
) => Promise<Data>;
|
|
419
|
+
},
|
|
420
|
+
ApiError
|
|
421
|
+
>({
|
|
422
|
+
api: new DataApi(),
|
|
423
|
+
errorType: ApiError,
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
function MyComponent() {
|
|
427
|
+
const { error, execute } = apiHooks.useGetData();
|
|
428
|
+
|
|
429
|
+
// error is now typed as ApiError | undefined
|
|
430
|
+
if (error) {
|
|
431
|
+
console.log('Status code:', error.statusCode); // TypeScript knows about statusCode
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Advanced Usage with Manual Query Management:**
|
|
437
|
+
|
|
438
|
+
```typescript jsx
|
|
439
|
+
import { createQueryApiHooks } from '@ahoo-wang/fetcher-react';
|
|
440
|
+
|
|
441
|
+
const apiHooks = createQueryApiHooks({ api: userApi });
|
|
442
|
+
|
|
443
|
+
function SearchComponent() {
|
|
444
|
+
const { loading, result, setQuery, getQuery } = apiHooks.useGetUsers({
|
|
445
|
+
initialQuery: { search: '', page: 1 },
|
|
446
|
+
autoExecute: false, // Manual execution control
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const handleSearch = (searchTerm: string) => {
|
|
450
|
+
// Update query without automatic execution
|
|
451
|
+
setQuery({ search: searchTerm, page: 1 });
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const handleSearchSubmit = () => {
|
|
455
|
+
// Manually execute with current query
|
|
456
|
+
apiHooks.useGetUsers().execute();
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const currentQuery = getQuery(); // Access current query parameters
|
|
460
|
+
|
|
461
|
+
return (
|
|
462
|
+
<div>
|
|
463
|
+
<input
|
|
464
|
+
value={currentQuery.search}
|
|
465
|
+
onChange={(e) => handleSearch(e.target.value)}
|
|
466
|
+
placeholder="Search users..."
|
|
467
|
+
/>
|
|
468
|
+
<button onClick={handleSearchSubmit} disabled={loading}>
|
|
469
|
+
{loading ? 'Searching...' : 'Search'}
|
|
470
|
+
</button>
|
|
471
|
+
{result?.map(user => (
|
|
472
|
+
<div key={user.id}>{user.name}</div>
|
|
473
|
+
))}
|
|
474
|
+
</div>
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
114
479
|
### Core Hooks
|
|
115
480
|
|
|
116
481
|
#### useExecutePromise Hook
|