@app-studio/web 0.9.18 → 0.9.20
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/dist/components/index.d.ts +0 -1
- package/dist/web.cjs.development.js +1 -2
- package/dist/web.cjs.development.js.map +1 -1
- package/dist/web.cjs.production.min.js +1 -1
- package/dist/web.cjs.production.min.js.map +1 -1
- package/dist/web.esm.js +1 -2
- package/dist/web.esm.js.map +1 -1
- package/dist/web.umd.development.js +5 -5
- package/dist/web.umd.development.js.map +1 -1
- package/dist/web.umd.production.min.js +1 -1
- package/dist/web.umd.production.min.js.map +1 -1
- package/docs/README.md +52 -0
- package/docs/adk-components.md +316 -0
- package/docs/adk-quick-start.md +294 -0
- package/docs/api-integration.md +801 -0
- package/docs/api-reference/README.md +103 -0
- package/docs/api-reference/data-display/flow.md +220 -0
- package/docs/api-reference/data-display/tree.md +210 -0
- package/docs/api-reference/form/chat-input.md +210 -0
- package/docs/api-reference/utility/button.md +145 -0
- package/docs/api-reference/utility/title.md +301 -0
- package/docs/app-studio.md +302 -0
- package/docs/component-development/guide.md +546 -0
- package/docs/contributing/documentation.md +153 -0
- package/docs/conventions.md +536 -0
- package/docs/design-system/theming.md +299 -0
- package/docs/documentation-system.md +143 -0
- package/docs/getting-started/component-usage.md +211 -0
- package/docs/getting-started/introduction.md +114 -0
- package/docs/guide.md +550 -0
- package/docs/integration-guide.md +449 -0
- package/docs/tutorials/README.md +51 -0
- package/docs/tutorials/basic/creating-a-simple-form.md +566 -0
- package/package.json +3 -2
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
# API Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to integrate with backend APIs in the Front-Starter project. The project uses OpenAPI-generated services for type-safe API calls.
|
|
4
|
+
|
|
5
|
+
## API Service Structure
|
|
6
|
+
|
|
7
|
+
API services are generated from OpenAPI/Swagger specifications and stored in the `src/services/api` directory. These services provide type-safe methods for interacting with the backend API.
|
|
8
|
+
|
|
9
|
+
Each service in the `src/services/api` directory contains:
|
|
10
|
+
|
|
11
|
+
1. **Base API functions** - Direct functions that return CancelablePromise objects
|
|
12
|
+
2. **Generated hooks** - React hooks that wrap the base functions with the `useRequest` hook from `@app-studio/react-request`
|
|
13
|
+
|
|
14
|
+
For example, a typical service like `UserService` contains functions like:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// Base API function
|
|
18
|
+
export const userControllerRead = (id: string): CancelablePromise<any> => {
|
|
19
|
+
return __request({
|
|
20
|
+
method: 'GET',
|
|
21
|
+
path: `/users/${id}`,
|
|
22
|
+
errors: {
|
|
23
|
+
404: `User not found`,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Generated hook that wraps the base function
|
|
29
|
+
export const useUserControllerReadService = ({ method = 'GET', ...options }: UseRequestOption = {}): {
|
|
30
|
+
run: (id: string) => void;
|
|
31
|
+
data: any;
|
|
32
|
+
} & UseRequestProperties => {
|
|
33
|
+
return useRequest(userControllerRead, { method, ...options });
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
These generated hooks provide a consistent interface for making API requests with built-in loading, error, and success states.
|
|
38
|
+
|
|
39
|
+
## Generating API Services
|
|
40
|
+
|
|
41
|
+
The project includes scripts to generate API services from OpenAPI/Swagger specifications:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Generate API services from local Swagger docs
|
|
45
|
+
npm run api:local
|
|
46
|
+
# or
|
|
47
|
+
yarn api:local
|
|
48
|
+
|
|
49
|
+
# Generate API services from remote Swagger docs
|
|
50
|
+
npm run api
|
|
51
|
+
# or
|
|
52
|
+
yarn api
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
These scripts generate TypeScript files in the `src/services/api` directory based on the API specification.
|
|
56
|
+
|
|
57
|
+
### How API Services Are Generated
|
|
58
|
+
|
|
59
|
+
The API services are automatically generated using the `react-api` tool with the following command:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
react-api --useUnionTypes --input http://localhost:3000/docs/swagger.json --output ./src/services/api && prettier --write ./src/services/api
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This command:
|
|
66
|
+
1. Uses the `react-api` tool from the `@app-studio/react-api` package
|
|
67
|
+
2. Enables union types with the `--useUnionTypes` flag for better TypeScript type safety
|
|
68
|
+
3. Reads the OpenAPI/Swagger specification from `http://localhost:3000/docs/swagger.json`
|
|
69
|
+
4. Outputs the generated TypeScript files to the `./src/services/api` directory
|
|
70
|
+
5. Formats the generated code with Prettier
|
|
71
|
+
|
|
72
|
+
The generated services include both the base API functions and React hooks that wrap these functions with the `useRequest` hook from `@app-studio/react-request`.
|
|
73
|
+
|
|
74
|
+
## API Configuration
|
|
75
|
+
|
|
76
|
+
The API base URL is configured in `src/configs/AppConfig.ts`:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
export const API_URL = env.REACT_APP_API_URL ? env.REACT_APP_API_URL : 'http://localhost:3000';
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The OpenAPI configuration is set up in `src/utils/request.ts`:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { OpenAPI } from 'src/services/api';
|
|
86
|
+
|
|
87
|
+
// Set the base URL for API requests
|
|
88
|
+
OpenAPI.BASE = API_URL;
|
|
89
|
+
OpenAPI.CORS = 'cors';
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Authentication
|
|
93
|
+
|
|
94
|
+
Authentication tokens are managed in the `AuthStore` and set in the OpenAPI configuration:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// Set the authentication token for API requests
|
|
98
|
+
export async function setToken(token: string) {
|
|
99
|
+
access_token = token;
|
|
100
|
+
OpenAPI.TOKEN = token;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Using Auth Hooks
|
|
105
|
+
|
|
106
|
+
The project provides several authentication-related hooks in the API services:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// Login example
|
|
110
|
+
import { AuthService } from 'src/services/api';
|
|
111
|
+
import { useAuthStore } from 'src/stores/AuthStore';
|
|
112
|
+
|
|
113
|
+
const LoginForm = () => {
|
|
114
|
+
const { setUser, setToken } = useAuthStore();
|
|
115
|
+
|
|
116
|
+
const loginRequest = AuthService.useAuthControllerLoginService({
|
|
117
|
+
onSuccess: (data) => {
|
|
118
|
+
// Store the token and user data
|
|
119
|
+
setToken(data.token);
|
|
120
|
+
setUser(data.user);
|
|
121
|
+
|
|
122
|
+
// Redirect to dashboard
|
|
123
|
+
navigate('/dashboard');
|
|
124
|
+
},
|
|
125
|
+
onError: (error) => {
|
|
126
|
+
showToast('error', 'Login Failed', error.message);
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const handleSubmit = (values) => {
|
|
131
|
+
loginRequest.run({
|
|
132
|
+
email: values.email,
|
|
133
|
+
password: values.password,
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<form onSubmit={handleSubmit}>
|
|
139
|
+
{/* Form fields */}
|
|
140
|
+
</form>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Handling Expired Tokens
|
|
146
|
+
|
|
147
|
+
To handle expired tokens, you can set up a global error handler in the request configuration:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// In src/utils/request.ts
|
|
151
|
+
import { OpenAPI, ApiError } from 'src/services/api';
|
|
152
|
+
import { useAuthStore } from 'src/stores/AuthStore';
|
|
153
|
+
|
|
154
|
+
// Set up global error handler
|
|
155
|
+
const originalFetch = window.fetch;
|
|
156
|
+
window.fetch = async (...args) => {
|
|
157
|
+
try {
|
|
158
|
+
const response = await originalFetch(...args);
|
|
159
|
+
|
|
160
|
+
// Handle 401 Unauthorized errors
|
|
161
|
+
if (response.status === 401) {
|
|
162
|
+
const { logout } = useAuthStore.getState();
|
|
163
|
+
logout();
|
|
164
|
+
window.location.href = '/login?expired=true';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return response;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('API request failed:', error);
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Making API Requests
|
|
176
|
+
|
|
177
|
+
### Request Hooks
|
|
178
|
+
|
|
179
|
+
The recommended way to make API requests is to create request hooks in `.request.ts` files. These hooks wrap the API services and provide loading, error, and success states.
|
|
180
|
+
|
|
181
|
+
#### Using Generated Hooks
|
|
182
|
+
|
|
183
|
+
The simplest approach is to use the generated hooks directly from the API services:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// example.component.tsx
|
|
187
|
+
import { UserService } from 'src/services/api';
|
|
188
|
+
|
|
189
|
+
export const UserProfile = ({ userId }) => {
|
|
190
|
+
const getUserRequest = UserService.useUserControllerReadService({
|
|
191
|
+
onSuccess: (data) => {
|
|
192
|
+
console.log('User data fetched:', data);
|
|
193
|
+
},
|
|
194
|
+
onError: (error) => {
|
|
195
|
+
console.error('Error fetching user:', error);
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
getUserRequest.run(userId);
|
|
201
|
+
}, [userId]);
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<div>
|
|
205
|
+
{getUserRequest.loading ? (
|
|
206
|
+
<p>Loading...</p>
|
|
207
|
+
) : getUserRequest.data ? (
|
|
208
|
+
<div>
|
|
209
|
+
<h2>{getUserRequest.data.name}</h2>
|
|
210
|
+
<p>{getUserRequest.data.email}</p>
|
|
211
|
+
</div>
|
|
212
|
+
) : null}
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Creating Custom Request Hooks
|
|
219
|
+
|
|
220
|
+
For more complex scenarios, create custom request hooks in `.request.ts` files that combine multiple API services:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// example.request.ts
|
|
224
|
+
import { useRequest } from '@app-studio/react-request';
|
|
225
|
+
import { ExampleService } from 'src/services/api';
|
|
226
|
+
|
|
227
|
+
export const useExampleRequests = (callbacks = {}) => {
|
|
228
|
+
const fetchDataRequest = useRequest({
|
|
229
|
+
request: ExampleService.exampleControllerFindAll,
|
|
230
|
+
onSuccess: callbacks.onFetchDataSuccess,
|
|
231
|
+
onError: callbacks.onFetchDataError,
|
|
232
|
+
onFetch: (params) => {
|
|
233
|
+
// This runs when the request is made
|
|
234
|
+
console.log('Fetching data with params:', params);
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const createItemRequest = useRequest({
|
|
239
|
+
request: ExampleService.exampleControllerCreate,
|
|
240
|
+
onSuccess: (data) => {
|
|
241
|
+
// You can refresh the data list after creating a new item
|
|
242
|
+
fetchDataRequest.run();
|
|
243
|
+
|
|
244
|
+
// And call the provided callback
|
|
245
|
+
if (callbacks.onCreateItemSuccess) {
|
|
246
|
+
callbacks.onCreateItemSuccess(data);
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
onError: callbacks.onCreateItemError,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
fetchDataRequest,
|
|
254
|
+
createItemRequest,
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### useRequest Options
|
|
260
|
+
|
|
261
|
+
The `useRequest` hook and the generated service hooks accept several options:
|
|
262
|
+
|
|
263
|
+
- `method`: HTTP method to use (GET, POST, etc.)
|
|
264
|
+
- `onSuccess`: Callback function that runs when the request succeeds
|
|
265
|
+
- `onError`: Callback function that runs when the request fails
|
|
266
|
+
- `onFetch`: Callback function that runs when the request is made
|
|
267
|
+
- `initialData`: Initial data to use before the request completes
|
|
268
|
+
- `manual`: Whether to run the request manually (default: true)
|
|
269
|
+
- `debounce`: Debounce time in milliseconds
|
|
270
|
+
- `throttle`: Throttle time in milliseconds
|
|
271
|
+
|
|
272
|
+
### Using Request Hooks in Components
|
|
273
|
+
|
|
274
|
+
Request hooks can be used in components to make API calls and handle responses:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// example.component.tsx
|
|
278
|
+
import React, { useEffect } from 'react';
|
|
279
|
+
import { useExampleRequests } from './example.request';
|
|
280
|
+
|
|
281
|
+
export const ExampleComponent = () => {
|
|
282
|
+
const { fetchDataRequest, createItemRequest } = useExampleRequests({
|
|
283
|
+
onFetchDataSuccess: (data) => {
|
|
284
|
+
console.log('Data fetched successfully:', data);
|
|
285
|
+
},
|
|
286
|
+
onFetchDataError: (error) => {
|
|
287
|
+
console.error('Error fetching data:', error);
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
// Fetch data when the component mounts
|
|
293
|
+
fetchDataRequest.run();
|
|
294
|
+
}, []);
|
|
295
|
+
|
|
296
|
+
const handleCreateItem = () => {
|
|
297
|
+
createItemRequest.run({
|
|
298
|
+
name: 'New Item',
|
|
299
|
+
description: 'This is a new item',
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
return (
|
|
304
|
+
<div>
|
|
305
|
+
{fetchDataRequest.loading ? (
|
|
306
|
+
<p>Loading...</p>
|
|
307
|
+
) : fetchDataRequest.error ? (
|
|
308
|
+
<p>Error: {fetchDataRequest.error.message}</p>
|
|
309
|
+
) : (
|
|
310
|
+
<ul>
|
|
311
|
+
{fetchDataRequest.data?.map((item) => (
|
|
312
|
+
<li key={item.id}>{item.name}</li>
|
|
313
|
+
))}
|
|
314
|
+
</ul>
|
|
315
|
+
)}
|
|
316
|
+
|
|
317
|
+
<button onClick={handleCreateItem} disabled={createItemRequest.loading}>
|
|
318
|
+
Create Item
|
|
319
|
+
</button>
|
|
320
|
+
</div>
|
|
321
|
+
);
|
|
322
|
+
};
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Error Handling
|
|
326
|
+
|
|
327
|
+
Error handling is an important part of API integration. The request hooks provide error states that can be used to display error messages to the user.
|
|
328
|
+
|
|
329
|
+
### Basic Error Handling
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
// Error handling in a component
|
|
333
|
+
if (fetchDataRequest.error) {
|
|
334
|
+
return (
|
|
335
|
+
<div>
|
|
336
|
+
<p>Error: {fetchDataRequest.error.message}</p>
|
|
337
|
+
<button onClick={() => fetchDataRequest.run()}>Retry</button>
|
|
338
|
+
</div>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Using showToast for User Feedback
|
|
344
|
+
|
|
345
|
+
You can use the `showToast` function from `@app-studio/web` to display error messages:
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
import { showToast } from '@app-studio/web';
|
|
349
|
+
|
|
350
|
+
// In the onError callback
|
|
351
|
+
onError: (error) => {
|
|
352
|
+
showToast('error', 'Error', error.message || 'An error occurred');
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Centralized Error Handling
|
|
357
|
+
|
|
358
|
+
For consistent error handling across your application, create a centralized error handler:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// utils/errorHandler.ts
|
|
362
|
+
import { showToast } from '@app-studio/web';
|
|
363
|
+
import { ApiError } from 'src/services/api';
|
|
364
|
+
|
|
365
|
+
export const handleApiError = (error: any, title = 'Error') => {
|
|
366
|
+
if (error instanceof ApiError) {
|
|
367
|
+
// Handle specific API error codes
|
|
368
|
+
switch (error.status) {
|
|
369
|
+
case 400:
|
|
370
|
+
showToast('error', title, 'Invalid request. Please check your input.');
|
|
371
|
+
break;
|
|
372
|
+
case 401:
|
|
373
|
+
showToast('error', title, 'Authentication required. Please log in again.');
|
|
374
|
+
// Redirect to login page
|
|
375
|
+
window.location.href = '/login';
|
|
376
|
+
break;
|
|
377
|
+
case 403:
|
|
378
|
+
showToast('error', title, 'You do not have permission to perform this action.');
|
|
379
|
+
break;
|
|
380
|
+
case 404:
|
|
381
|
+
showToast('error', title, 'The requested resource was not found.');
|
|
382
|
+
break;
|
|
383
|
+
case 500:
|
|
384
|
+
showToast('error', title, 'Server error. Please try again later.');
|
|
385
|
+
break;
|
|
386
|
+
default:
|
|
387
|
+
showToast('error', title, error.message || 'An unexpected error occurred.');
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
// Handle non-API errors
|
|
391
|
+
showToast('error', title, error.message || 'An unexpected error occurred.');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Log the error for debugging
|
|
395
|
+
console.error('API Error:', error);
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// Using the centralized error handler in your request hooks
|
|
399
|
+
const fetchDataRequest = useRequest({
|
|
400
|
+
request: ExampleService.exampleControllerFindAll,
|
|
401
|
+
onSuccess: (data) => {
|
|
402
|
+
// Handle success
|
|
403
|
+
},
|
|
404
|
+
onError: (error) => {
|
|
405
|
+
handleApiError(error, 'Data Fetch Failed');
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Error Boundaries for React Components
|
|
411
|
+
|
|
412
|
+
Use React Error Boundaries to catch and handle errors in your components:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
// components/ErrorBoundary.tsx
|
|
416
|
+
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
|
417
|
+
|
|
418
|
+
interface Props {
|
|
419
|
+
children: ReactNode;
|
|
420
|
+
fallback?: ReactNode;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
interface State {
|
|
424
|
+
hasError: boolean;
|
|
425
|
+
error?: Error;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
class ErrorBoundary extends Component<Props, State> {
|
|
429
|
+
constructor(props: Props) {
|
|
430
|
+
super(props);
|
|
431
|
+
this.state = { hasError: false };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
static getDerivedStateFromError(error: Error): State {
|
|
435
|
+
return { hasError: true, error };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
|
439
|
+
console.error('Component error:', error, errorInfo);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
render(): ReactNode {
|
|
443
|
+
if (this.state.hasError) {
|
|
444
|
+
return this.props.fallback || (
|
|
445
|
+
<div>
|
|
446
|
+
<h2>Something went wrong.</h2>
|
|
447
|
+
<p>{this.state.error?.message}</p>
|
|
448
|
+
<button onClick={() => this.setState({ hasError: false })}>Try again</button>
|
|
449
|
+
</div>
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return this.props.children;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Using the error boundary in your components
|
|
458
|
+
const DataComponent = () => (
|
|
459
|
+
<ErrorBoundary>
|
|
460
|
+
<UserList />
|
|
461
|
+
</ErrorBoundary>
|
|
462
|
+
);
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Pagination and Filtering
|
|
466
|
+
|
|
467
|
+
For paginated API endpoints, you can pass pagination parameters to the request:
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
// Fetch paginated data
|
|
471
|
+
fetchDataRequest.run({
|
|
472
|
+
take: 10,
|
|
473
|
+
skip: (currentPage - 1) * 10,
|
|
474
|
+
filter: searchTerm,
|
|
475
|
+
sortBy: 'createdAt',
|
|
476
|
+
sortOrder: 'desc',
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## Caching and Data Persistence
|
|
481
|
+
|
|
482
|
+
The request hooks do not include built-in caching. If you need to cache API responses, you can store the data in a Zustand store:
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
// stores/DataStore.ts
|
|
486
|
+
import { create } from 'zustand';
|
|
487
|
+
|
|
488
|
+
interface DataState {
|
|
489
|
+
items: any[];
|
|
490
|
+
setItems: (items: any[]) => void;
|
|
491
|
+
isLoading: boolean;
|
|
492
|
+
setLoading: (isLoading: boolean) => void;
|
|
493
|
+
error: Error | null;
|
|
494
|
+
setError: (error: Error | null) => void;
|
|
495
|
+
fetchItems: () => Promise<void>;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export const useDataStore = create<DataState>((set, get) => ({
|
|
499
|
+
items: [],
|
|
500
|
+
setItems: (items) => set({ items }),
|
|
501
|
+
isLoading: false,
|
|
502
|
+
setLoading: (isLoading) => set({ isLoading }),
|
|
503
|
+
error: null,
|
|
504
|
+
setError: (error) => set({ error }),
|
|
505
|
+
|
|
506
|
+
// Fetch items from the API
|
|
507
|
+
fetchItems: async () => {
|
|
508
|
+
try {
|
|
509
|
+
set({ isLoading: true, error: null });
|
|
510
|
+
|
|
511
|
+
// Import the API service directly in the action
|
|
512
|
+
const { ExampleService } = await import('src/services/api');
|
|
513
|
+
|
|
514
|
+
// Call the API directly (not using hooks in stores)
|
|
515
|
+
const data = await ExampleService.exampleControllerFindAll();
|
|
516
|
+
|
|
517
|
+
set({ items: data, isLoading: false });
|
|
518
|
+
return data;
|
|
519
|
+
} catch (error) {
|
|
520
|
+
set({ error: error as Error, isLoading: false });
|
|
521
|
+
throw error;
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
}));
|
|
525
|
+
|
|
526
|
+
// In your component
|
|
527
|
+
import { useDataStore } from 'src/stores/DataStore';
|
|
528
|
+
import { useEffect } from 'react';
|
|
529
|
+
|
|
530
|
+
const ExampleComponent = () => {
|
|
531
|
+
const { items, isLoading, error, fetchItems } = useDataStore();
|
|
532
|
+
|
|
533
|
+
useEffect(() => {
|
|
534
|
+
fetchItems();
|
|
535
|
+
}, []);
|
|
536
|
+
|
|
537
|
+
return (
|
|
538
|
+
<div>
|
|
539
|
+
{isLoading ? (
|
|
540
|
+
<p>Loading...</p>
|
|
541
|
+
) : error ? (
|
|
542
|
+
<p>Error: {error.message}</p>
|
|
543
|
+
) : (
|
|
544
|
+
<ul>
|
|
545
|
+
{items.map((item) => (
|
|
546
|
+
<li key={item.id}>{item.name}</li>
|
|
547
|
+
))}
|
|
548
|
+
</ul>
|
|
549
|
+
)}
|
|
550
|
+
</div>
|
|
551
|
+
);
|
|
552
|
+
};
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Integrating API Hooks with Zustand Stores
|
|
556
|
+
|
|
557
|
+
For more complex state management, you can combine API hooks with Zustand stores:
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
// Component using both API hooks and Zustand store
|
|
561
|
+
import { useEffect } from 'react';
|
|
562
|
+
import { UserService } from 'src/services/api';
|
|
563
|
+
import { useDataStore } from 'src/stores/DataStore';
|
|
564
|
+
|
|
565
|
+
const UserList = () => {
|
|
566
|
+
// API hook for fetching users
|
|
567
|
+
const getUsersRequest = UserService.useUserControllerFindService({
|
|
568
|
+
onSuccess: (data) => {
|
|
569
|
+
// Update the store with the fetched data
|
|
570
|
+
setItems(data);
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// Zustand store for caching the data
|
|
575
|
+
const { items, setItems } = useDataStore();
|
|
576
|
+
|
|
577
|
+
useEffect(() => {
|
|
578
|
+
// If we already have cached data, don't fetch again
|
|
579
|
+
if (items.length === 0) {
|
|
580
|
+
getUsersRequest.run();
|
|
581
|
+
}
|
|
582
|
+
}, []);
|
|
583
|
+
|
|
584
|
+
return (
|
|
585
|
+
<div>
|
|
586
|
+
{getUsersRequest.loading ? (
|
|
587
|
+
<p>Loading...</p>
|
|
588
|
+
) : getUsersRequest.error ? (
|
|
589
|
+
<p>Error: {getUsersRequest.error.message}</p>
|
|
590
|
+
) : (
|
|
591
|
+
<ul>
|
|
592
|
+
{items.map((user) => (
|
|
593
|
+
<li key={user.id}>{user.name}</li>
|
|
594
|
+
))}
|
|
595
|
+
</ul>
|
|
596
|
+
)}
|
|
597
|
+
</div>
|
|
598
|
+
);
|
|
599
|
+
};
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## File Uploads
|
|
603
|
+
|
|
604
|
+
For file uploads, you can use the `MediaUploader` component:
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
import { MediaUploader } from 'src/components/MediaUploader';
|
|
608
|
+
|
|
609
|
+
<MediaUploader
|
|
610
|
+
onUpload={(file) => {
|
|
611
|
+
uploadFileRequest.run({
|
|
612
|
+
file,
|
|
613
|
+
type: 'image',
|
|
614
|
+
});
|
|
615
|
+
}}
|
|
616
|
+
accept="image/*"
|
|
617
|
+
/>
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
## Websockets and Real-time Data
|
|
621
|
+
|
|
622
|
+
For real-time data, you can use the `multimodalLiveClient` utility:
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
import { LiveClientSetup } from 'src/lib/multimodalLiveClient';
|
|
626
|
+
|
|
627
|
+
// Set up a real-time connection
|
|
628
|
+
const client = new LiveClientSetup({
|
|
629
|
+
url: 'wss://api.example.com/ws',
|
|
630
|
+
token: authToken,
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// Listen for messages
|
|
634
|
+
client.on('message', (message) => {
|
|
635
|
+
console.log('Received message:', message);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// Send a message
|
|
639
|
+
client.send({
|
|
640
|
+
type: 'chat',
|
|
641
|
+
content: 'Hello, world!',
|
|
642
|
+
});
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
## Advanced API Integration Patterns
|
|
646
|
+
|
|
647
|
+
### Request Orchestration
|
|
648
|
+
|
|
649
|
+
For complex pages that require multiple API calls, it's recommended to create a centralized request module like `editor.request.ts`. This approach provides several benefits:
|
|
650
|
+
|
|
651
|
+
1. **Centralized API logic**: All API calls are defined in one place
|
|
652
|
+
2. **Coordinated data fetching**: Easily chain and coordinate multiple API calls
|
|
653
|
+
3. **Reusable request hooks**: Create custom hooks that can be used across components
|
|
654
|
+
4. **Consistent error handling**: Implement consistent error handling for all API calls
|
|
655
|
+
|
|
656
|
+
Here's an example based on the pattern used in `src/pages/editor/editor.request.ts`:
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
// page.request.ts
|
|
660
|
+
import {
|
|
661
|
+
UserService,
|
|
662
|
+
ContentService,
|
|
663
|
+
CommentService
|
|
664
|
+
} from 'src/services/api';
|
|
665
|
+
|
|
666
|
+
const usePageRequests = ({
|
|
667
|
+
// Callbacks for different operations
|
|
668
|
+
getUserCallback = (data) => {},
|
|
669
|
+
getContentCallback = (data) => {},
|
|
670
|
+
getCommentsCallback = (data) => {},
|
|
671
|
+
onError = () => {},
|
|
672
|
+
}) => {
|
|
673
|
+
// Get user data
|
|
674
|
+
const getUserRequest = UserService.useUserControllerReadService({
|
|
675
|
+
onSuccess: (data) => {
|
|
676
|
+
getUserCallback(data);
|
|
677
|
+
|
|
678
|
+
// After getting user data, fetch their content
|
|
679
|
+
if (data?.id) {
|
|
680
|
+
getContentRequest.run({ userId: data.id });
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
onError,
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// Get content data
|
|
687
|
+
const getContentRequest = ContentService.useContentControllerFindService({
|
|
688
|
+
onSuccess: (data) => {
|
|
689
|
+
getContentCallback(data);
|
|
690
|
+
|
|
691
|
+
// After getting content, fetch comments for each content item
|
|
692
|
+
if (data?.items?.length) {
|
|
693
|
+
data.items.forEach(item => {
|
|
694
|
+
getCommentsRequest.run({ contentId: item.id });
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
onError,
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// Get comments
|
|
702
|
+
const getCommentsRequest = CommentService.useCommentControllerFindService({
|
|
703
|
+
onSuccess: getCommentsCallback,
|
|
704
|
+
onError,
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
// Create comment
|
|
708
|
+
const createCommentRequest = CommentService.useCommentControllerCreateService({
|
|
709
|
+
onSuccess: (data) => {
|
|
710
|
+
// Refresh comments after creating a new one
|
|
711
|
+
getCommentsRequest.run({ contentId: data.contentId });
|
|
712
|
+
},
|
|
713
|
+
onError,
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
return {
|
|
717
|
+
getUserRequest,
|
|
718
|
+
getContentRequest,
|
|
719
|
+
getCommentsRequest,
|
|
720
|
+
createCommentRequest,
|
|
721
|
+
};
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
export default usePageRequests;
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### Composing Multiple API Requests
|
|
728
|
+
|
|
729
|
+
When working with complex data flows, you often need to chain API requests together. Here are some common patterns:
|
|
730
|
+
|
|
731
|
+
#### Sequential Requests
|
|
732
|
+
|
|
733
|
+
Use the `onSuccess` callback to trigger dependent requests:
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
const getPageRequest = PageService.usePageControllerReadService({
|
|
737
|
+
onSuccess: (data) => {
|
|
738
|
+
// Store the page data
|
|
739
|
+
getPageCallback(data);
|
|
740
|
+
|
|
741
|
+
// If the page has a workflow, fetch it
|
|
742
|
+
if (data?.workflowId) {
|
|
743
|
+
getWorkflowRequest.run(data.workflowId);
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
});
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
#### Refreshing Data After Mutations
|
|
750
|
+
|
|
751
|
+
After creating, updating, or deleting data, refresh the relevant data:
|
|
752
|
+
|
|
753
|
+
```typescript
|
|
754
|
+
const deleteComponentRequest = ComponentService.useComponentControllerDeleteService({
|
|
755
|
+
onSuccess: () => {
|
|
756
|
+
// Refresh the page data after deleting a component
|
|
757
|
+
getPageRequest.run(id);
|
|
758
|
+
},
|
|
759
|
+
});
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
#### Handling Request Dependencies
|
|
763
|
+
|
|
764
|
+
When one request depends on the result of another:
|
|
765
|
+
|
|
766
|
+
```typescript
|
|
767
|
+
// First, get the user
|
|
768
|
+
getUserRequest.run(userId);
|
|
769
|
+
|
|
770
|
+
// In the onSuccess callback of getUserRequest
|
|
771
|
+
onSuccess: (userData) => {
|
|
772
|
+
// Then get the user's content
|
|
773
|
+
getContentRequest.run({ userId: userData.id });
|
|
774
|
+
|
|
775
|
+
// In the onSuccess callback of getContentRequest
|
|
776
|
+
onSuccess: (contentData) => {
|
|
777
|
+
// Finally, get comments for each content item
|
|
778
|
+
contentData.items.forEach(item => {
|
|
779
|
+
getCommentsRequest.run({ contentId: item.id });
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
## Best Practices
|
|
786
|
+
|
|
787
|
+
1. **Use Generated API Hooks**: Leverage the hooks from `@src/services/api/` for type-safe API calls
|
|
788
|
+
2. **Create Request Modules**: For complex pages, create dedicated `.request.ts` files that orchestrate multiple API calls
|
|
789
|
+
3. **Chain Requests Properly**: Use `onSuccess` callbacks to chain dependent requests
|
|
790
|
+
4. **Handle Loading States**: Always show loading indicators when requests are in progress
|
|
791
|
+
5. **Implement Error Handling**: Use `onError` callbacks to handle errors consistently
|
|
792
|
+
6. **Refresh Data After Mutations**: Update relevant data after create, update, or delete operations
|
|
793
|
+
7. **Use Callbacks for Component Communication**: Pass callback functions to request hooks to communicate with components
|
|
794
|
+
8. **Type Safety**: Use the generated TypeScript types for request parameters and responses
|
|
795
|
+
9. **Pagination**: Implement pagination for large data sets
|
|
796
|
+
10. **Caching**: Consider using Zustand stores to cache frequently accessed data
|
|
797
|
+
11. **Authentication**: Ensure authentication tokens are properly managed in the AuthStore
|
|
798
|
+
12. **Validation**: Validate user input before sending it to the API
|
|
799
|
+
13. **Testing**: Write tests for API integration to ensure it works as expected
|
|
800
|
+
|
|
801
|
+
By following these guidelines and leveraging the patterns from the codebase, you can create robust and maintainable API integration in your Front-Starter project.
|