cursor-kit-cli 1.2.0-beta → 1.2.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/dist/cli.cjs +333 -56
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +334 -57
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +39 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +33 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/commands/docs.md +5 -3
- package/templates/commands/explain.md +5 -3
- package/templates/commands/fix.md +5 -3
- package/templates/commands/implement.md +5 -3
- package/templates/commands/refactor.md +5 -3
- package/templates/commands/review.md +5 -3
- package/templates/commands/test.md +5 -3
- package/templates/manifest.json +11 -8
- package/templates/rules/git.mdc +0 -2
- package/templates/rules/toc.mdc +17 -9
- package/templates/skills/aesthetic/SKILL.md +121 -0
- package/templates/skills/aesthetic/assets/design-guideline-template.md +163 -0
- package/templates/skills/aesthetic/assets/design-story-template.md +135 -0
- package/templates/skills/aesthetic/references/design-principles.md +62 -0
- package/templates/skills/aesthetic/references/design-resources.md +75 -0
- package/templates/skills/aesthetic/references/micro-interactions.md +53 -0
- package/templates/skills/aesthetic/references/storytelling-design.md +50 -0
- package/templates/skills/backend-development/SKILL.mdc +95 -0
- package/templates/skills/backend-development/references/backend-api-design.md +495 -0
- package/templates/skills/backend-development/references/backend-architecture.md +454 -0
- package/templates/skills/backend-development/references/backend-authentication.md +338 -0
- package/templates/skills/backend-development/references/backend-code-quality.md +659 -0
- package/templates/skills/backend-development/references/backend-debugging.md +904 -0
- package/templates/skills/backend-development/references/backend-devops.md +494 -0
- package/templates/skills/backend-development/references/backend-mindset.md +387 -0
- package/templates/skills/backend-development/references/backend-performance.md +397 -0
- package/templates/skills/backend-development/references/backend-security.md +290 -0
- package/templates/skills/backend-development/references/backend-technologies.md +256 -0
- package/templates/skills/backend-development/references/backend-testing.md +429 -0
- package/templates/skills/frontend-design/SKILL.mdc +41 -0
- package/templates/skills/frontend-design/references/animejs.md +396 -0
- package/templates/skills/frontend-development/SKILL.mdc +399 -0
- package/templates/skills/frontend-development/resources/common-patterns.md +331 -0
- package/templates/skills/frontend-development/resources/complete-examples.md +872 -0
- package/templates/skills/frontend-development/resources/component-patterns.md +502 -0
- package/templates/skills/frontend-development/resources/data-fetching.md +767 -0
- package/templates/skills/frontend-development/resources/file-organization.md +502 -0
- package/templates/skills/frontend-development/resources/loading-and-error-states.md +501 -0
- package/templates/skills/frontend-development/resources/performance.md +406 -0
- package/templates/skills/frontend-development/resources/routing-guide.md +364 -0
- package/templates/skills/frontend-development/resources/styling-guide.md +428 -0
- package/templates/skills/frontend-development/resources/typescript-standards.md +418 -0
- package/templates/skills/problem-solving/SKILL.mdc +96 -0
- package/templates/skills/problem-solving/references/attribution.md +69 -0
- package/templates/skills/problem-solving/references/collision-zone-thinking.md +79 -0
- package/templates/skills/problem-solving/references/inversion-exercise.md +91 -0
- package/templates/skills/problem-solving/references/meta-pattern-recognition.md +87 -0
- package/templates/skills/problem-solving/references/scale-game.md +95 -0
- package/templates/skills/problem-solving/references/simplification-cascades.md +80 -0
- package/templates/skills/problem-solving/references/when-stuck.md +72 -0
- package/templates/skills/research/SKILL.mdc +168 -0
- package/templates/skills/sequential-thinking/.env.example +8 -0
- package/templates/skills/sequential-thinking/README.md +183 -0
- package/templates/skills/sequential-thinking/SKILL.mdc +94 -0
- package/templates/skills/sequential-thinking/package.json +31 -0
- package/templates/skills/sequential-thinking/references/advanced-strategies.md +79 -0
- package/templates/skills/sequential-thinking/references/advanced-techniques.md +76 -0
- package/templates/skills/sequential-thinking/references/core-patterns.md +95 -0
- package/templates/skills/sequential-thinking/references/examples-api.md +88 -0
- package/templates/skills/sequential-thinking/references/examples-architecture.md +94 -0
- package/templates/skills/sequential-thinking/references/examples-debug.md +90 -0
- package/templates/skills/sequential-thinking/scripts/format-thought.js +159 -0
- package/templates/skills/sequential-thinking/scripts/process-thought.js +236 -0
- package/templates/skills/sequential-thinking/tests/format-thought.test.js +133 -0
- package/templates/skills/sequential-thinking/tests/process-thought.test.js +215 -0
- package/templates/skills/ui-styling/LICENSE.txt +202 -0
- package/templates/skills/ui-styling/SKILL.mdc +321 -0
- package/templates/skills/ui-styling/references/canvas-design-system.md +320 -0
- package/templates/skills/ui-styling/references/shadcn-accessibility.md +471 -0
- package/templates/skills/ui-styling/references/shadcn-components.md +424 -0
- package/templates/skills/ui-styling/references/shadcn-theming.md +373 -0
- package/templates/skills/ui-styling/references/tailwind-customization.md +483 -0
- package/templates/skills/ui-styling/references/tailwind-responsive.md +382 -0
- package/templates/skills/ui-styling/references/tailwind-utilities.md +455 -0
- package/templates/rules/frontend-design.mdc +0 -48
- package/templates/rules/performance.mdc +0 -54
- package/templates/rules/react.mdc +0 -58
- package/templates/rules/security.mdc +0 -50
- package/templates/rules/testing.mdc +0 -54
- package/templates/rules/typescript.mdc +0 -36
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
# Loading & Error States
|
|
2
|
+
|
|
3
|
+
**CRITICAL**: Proper loading and error state handling prevents layout shift and provides better user experience.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ⚠️ CRITICAL RULE: Never Use Early Returns
|
|
8
|
+
|
|
9
|
+
### The Problem
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// ❌ NEVER DO THIS - Early return with loading spinner
|
|
13
|
+
const Component = () => {
|
|
14
|
+
const { data, isLoading } = useQuery();
|
|
15
|
+
|
|
16
|
+
// WRONG: This causes layout shift and poor UX
|
|
17
|
+
if (isLoading) {
|
|
18
|
+
return <LoadingSpinner />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return <Content data={data} />;
|
|
22
|
+
};
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Why this is bad:**
|
|
26
|
+
1. **Layout Shift**: Content position jumps when loading completes
|
|
27
|
+
2. **CLS (Cumulative Layout Shift)**: Poor Core Web Vital score
|
|
28
|
+
3. **Jarring UX**: Page structure changes suddenly
|
|
29
|
+
4. **Lost Scroll Position**: User loses place on page
|
|
30
|
+
|
|
31
|
+
### The Solutions
|
|
32
|
+
|
|
33
|
+
**Option 1: SuspenseLoader (PREFERRED for new components)**
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { SuspenseLoader } from '~components/SuspenseLoader';
|
|
37
|
+
|
|
38
|
+
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
|
|
39
|
+
|
|
40
|
+
export const MyComponent: React.FC = () => {
|
|
41
|
+
return (
|
|
42
|
+
<SuspenseLoader>
|
|
43
|
+
<HeavyComponent />
|
|
44
|
+
</SuspenseLoader>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Option 2: LoadingOverlay (for legacy useQuery patterns)**
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { LoadingOverlay } from '~components/LoadingOverlay';
|
|
53
|
+
|
|
54
|
+
export const MyComponent: React.FC = () => {
|
|
55
|
+
const { data, isLoading } = useQuery({ ... });
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<LoadingOverlay loading={isLoading}>
|
|
59
|
+
<Content data={data} />
|
|
60
|
+
</LoadingOverlay>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## SuspenseLoader Component
|
|
68
|
+
|
|
69
|
+
### What It Does
|
|
70
|
+
|
|
71
|
+
- Shows loading indicator while lazy components load
|
|
72
|
+
- Smooth fade-in animation
|
|
73
|
+
- Prevents layout shift
|
|
74
|
+
- Consistent loading experience across app
|
|
75
|
+
|
|
76
|
+
### Import
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { SuspenseLoader } from '~components/SuspenseLoader';
|
|
80
|
+
// Or
|
|
81
|
+
import { SuspenseLoader } from '@/components/SuspenseLoader';
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Basic Usage
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
<SuspenseLoader>
|
|
88
|
+
<LazyLoadedComponent />
|
|
89
|
+
</SuspenseLoader>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### With useSuspenseQuery
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { useSuspenseQuery } from '@tanstack/react-query';
|
|
96
|
+
import { SuspenseLoader } from '~components/SuspenseLoader';
|
|
97
|
+
|
|
98
|
+
const Inner: React.FC = () => {
|
|
99
|
+
// No isLoading needed!
|
|
100
|
+
const { data } = useSuspenseQuery({
|
|
101
|
+
queryKey: ['data'],
|
|
102
|
+
queryFn: () => api.getData(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return <Display data={data} />;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Outer component wraps in Suspense
|
|
109
|
+
export const Outer: React.FC = () => {
|
|
110
|
+
return (
|
|
111
|
+
<SuspenseLoader>
|
|
112
|
+
<Inner />
|
|
113
|
+
</SuspenseLoader>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Multiple Suspense Boundaries
|
|
119
|
+
|
|
120
|
+
**Pattern**: Separate loading for independent sections
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
export const Dashboard: React.FC = () => {
|
|
124
|
+
return (
|
|
125
|
+
<Box>
|
|
126
|
+
<SuspenseLoader>
|
|
127
|
+
<Header />
|
|
128
|
+
</SuspenseLoader>
|
|
129
|
+
|
|
130
|
+
<SuspenseLoader>
|
|
131
|
+
<MainContent />
|
|
132
|
+
</SuspenseLoader>
|
|
133
|
+
|
|
134
|
+
<SuspenseLoader>
|
|
135
|
+
<Sidebar />
|
|
136
|
+
</SuspenseLoader>
|
|
137
|
+
</Box>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Benefits:**
|
|
143
|
+
- Each section loads independently
|
|
144
|
+
- User sees partial content sooner
|
|
145
|
+
- Better perceived performance
|
|
146
|
+
|
|
147
|
+
### Nested Suspense
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
export const ParentComponent: React.FC = () => {
|
|
151
|
+
return (
|
|
152
|
+
<SuspenseLoader>
|
|
153
|
+
{/* Parent suspends while loading */}
|
|
154
|
+
<ParentContent>
|
|
155
|
+
<SuspenseLoader>
|
|
156
|
+
{/* Nested suspense for child */}
|
|
157
|
+
<ChildComponent />
|
|
158
|
+
</SuspenseLoader>
|
|
159
|
+
</ParentContent>
|
|
160
|
+
</SuspenseLoader>
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## LoadingOverlay Component
|
|
168
|
+
|
|
169
|
+
### When to Use
|
|
170
|
+
|
|
171
|
+
- Legacy components with `useQuery` (not refactored to Suspense yet)
|
|
172
|
+
- Overlay loading state needed
|
|
173
|
+
- Can't use Suspense boundaries
|
|
174
|
+
|
|
175
|
+
### Usage
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { LoadingOverlay } from '~components/LoadingOverlay';
|
|
179
|
+
|
|
180
|
+
export const MyComponent: React.FC = () => {
|
|
181
|
+
const { data, isLoading } = useQuery({
|
|
182
|
+
queryKey: ['data'],
|
|
183
|
+
queryFn: () => api.getData(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<LoadingOverlay loading={isLoading}>
|
|
188
|
+
<Box sx={{ p: 2 }}>
|
|
189
|
+
{data && <Content data={data} />}
|
|
190
|
+
</Box>
|
|
191
|
+
</LoadingOverlay>
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**What it does:**
|
|
197
|
+
- Shows semi-transparent overlay with spinner
|
|
198
|
+
- Content area reserved (no layout shift)
|
|
199
|
+
- Prevents interaction while loading
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Error Handling
|
|
204
|
+
|
|
205
|
+
### useMuiSnackbar Hook (REQUIRED)
|
|
206
|
+
|
|
207
|
+
**NEVER use react-toastify** - Project standard is MUI Snackbar
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
|
|
211
|
+
|
|
212
|
+
export const MyComponent: React.FC = () => {
|
|
213
|
+
const { showSuccess, showError, showInfo, showWarning } = useMuiSnackbar();
|
|
214
|
+
|
|
215
|
+
const handleAction = async () => {
|
|
216
|
+
try {
|
|
217
|
+
await api.doSomething();
|
|
218
|
+
showSuccess('Operation completed successfully');
|
|
219
|
+
} catch (error) {
|
|
220
|
+
showError('Operation failed');
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
return <Button onClick={handleAction}>Do Action</Button>;
|
|
225
|
+
};
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Available Methods:**
|
|
229
|
+
- `showSuccess(message)` - Green success message
|
|
230
|
+
- `showError(message)` - Red error message
|
|
231
|
+
- `showWarning(message)` - Orange warning message
|
|
232
|
+
- `showInfo(message)` - Blue info message
|
|
233
|
+
|
|
234
|
+
### TanStack Query Error Callbacks
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { useSuspenseQuery } from '@tanstack/react-query';
|
|
238
|
+
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
|
|
239
|
+
|
|
240
|
+
export const MyComponent: React.FC = () => {
|
|
241
|
+
const { showError } = useMuiSnackbar();
|
|
242
|
+
|
|
243
|
+
const { data } = useSuspenseQuery({
|
|
244
|
+
queryKey: ['data'],
|
|
245
|
+
queryFn: () => api.getData(),
|
|
246
|
+
|
|
247
|
+
// Handle errors
|
|
248
|
+
onError: (error) => {
|
|
249
|
+
showError('Failed to load data');
|
|
250
|
+
console.error('Query error:', error);
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return <Content data={data} />;
|
|
255
|
+
};
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Error Boundaries
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
262
|
+
|
|
263
|
+
function ErrorFallback({ error, resetErrorBoundary }) {
|
|
264
|
+
return (
|
|
265
|
+
<Box sx={{ p: 4, textAlign: 'center' }}>
|
|
266
|
+
<Typography variant='h5' color='error'>
|
|
267
|
+
Something went wrong
|
|
268
|
+
</Typography>
|
|
269
|
+
<Typography>{error.message}</Typography>
|
|
270
|
+
<Button onClick={resetErrorBoundary}>Try Again</Button>
|
|
271
|
+
</Box>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export const MyPage: React.FC = () => {
|
|
276
|
+
return (
|
|
277
|
+
<ErrorBoundary
|
|
278
|
+
FallbackComponent={ErrorFallback}
|
|
279
|
+
onError={(error) => console.error('Boundary caught:', error)}
|
|
280
|
+
>
|
|
281
|
+
<SuspenseLoader>
|
|
282
|
+
<ComponentThatMightError />
|
|
283
|
+
</SuspenseLoader>
|
|
284
|
+
</ErrorBoundary>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Complete Examples
|
|
292
|
+
|
|
293
|
+
### Example 1: Modern Component with Suspense
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import React from 'react';
|
|
297
|
+
import { Box, Paper } from '@mui/material';
|
|
298
|
+
import { useSuspenseQuery } from '@tanstack/react-query';
|
|
299
|
+
import { SuspenseLoader } from '~components/SuspenseLoader';
|
|
300
|
+
import { myFeatureApi } from '../api/myFeatureApi';
|
|
301
|
+
|
|
302
|
+
// Inner component uses useSuspenseQuery
|
|
303
|
+
const InnerComponent: React.FC<{ id: number }> = ({ id }) => {
|
|
304
|
+
const { data } = useSuspenseQuery({
|
|
305
|
+
queryKey: ['entity', id],
|
|
306
|
+
queryFn: () => myFeatureApi.getEntity(id),
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// data is always defined - no isLoading needed!
|
|
310
|
+
return (
|
|
311
|
+
<Paper sx={{ p: 2 }}>
|
|
312
|
+
<h2>{data.title}</h2>
|
|
313
|
+
<p>{data.description}</p>
|
|
314
|
+
</Paper>
|
|
315
|
+
);
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// Outer component provides Suspense boundary
|
|
319
|
+
export const OuterComponent: React.FC<{ id: number }> = ({ id }) => {
|
|
320
|
+
return (
|
|
321
|
+
<Box>
|
|
322
|
+
<SuspenseLoader>
|
|
323
|
+
<InnerComponent id={id} />
|
|
324
|
+
</SuspenseLoader>
|
|
325
|
+
</Box>
|
|
326
|
+
);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
export default OuterComponent;
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Example 2: Legacy Pattern with LoadingOverlay
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import React from 'react';
|
|
336
|
+
import { Box } from '@mui/material';
|
|
337
|
+
import { useQuery } from '@tanstack/react-query';
|
|
338
|
+
import { LoadingOverlay } from '~components/LoadingOverlay';
|
|
339
|
+
import { myFeatureApi } from '../api/myFeatureApi';
|
|
340
|
+
|
|
341
|
+
export const LegacyComponent: React.FC<{ id: number }> = ({ id }) => {
|
|
342
|
+
const { data, isLoading, error } = useQuery({
|
|
343
|
+
queryKey: ['entity', id],
|
|
344
|
+
queryFn: () => myFeatureApi.getEntity(id),
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<LoadingOverlay loading={isLoading}>
|
|
349
|
+
<Box sx={{ p: 2 }}>
|
|
350
|
+
{error && <ErrorDisplay error={error} />}
|
|
351
|
+
{data && <Content data={data} />}
|
|
352
|
+
</Box>
|
|
353
|
+
</LoadingOverlay>
|
|
354
|
+
);
|
|
355
|
+
};
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Example 3: Error Handling with Snackbar
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import React from 'react';
|
|
362
|
+
import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
363
|
+
import { Button } from '@mui/material';
|
|
364
|
+
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
|
|
365
|
+
import { myFeatureApi } from '../api/myFeatureApi';
|
|
366
|
+
|
|
367
|
+
export const EntityEditor: React.FC<{ id: number }> = ({ id }) => {
|
|
368
|
+
const queryClient = useQueryClient();
|
|
369
|
+
const { showSuccess, showError } = useMuiSnackbar();
|
|
370
|
+
|
|
371
|
+
const { data } = useSuspenseQuery({
|
|
372
|
+
queryKey: ['entity', id],
|
|
373
|
+
queryFn: () => myFeatureApi.getEntity(id),
|
|
374
|
+
onError: () => {
|
|
375
|
+
showError('Failed to load entity');
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const updateMutation = useMutation({
|
|
380
|
+
mutationFn: (updates) => myFeatureApi.update(id, updates),
|
|
381
|
+
|
|
382
|
+
onSuccess: () => {
|
|
383
|
+
queryClient.invalidateQueries({ queryKey: ['entity', id] });
|
|
384
|
+
showSuccess('Entity updated successfully');
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
onError: () => {
|
|
388
|
+
showError('Failed to update entity');
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
return (
|
|
393
|
+
<Button onClick={() => updateMutation.mutate({ name: 'New' })}>
|
|
394
|
+
Update
|
|
395
|
+
</Button>
|
|
396
|
+
);
|
|
397
|
+
};
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## Loading State Anti-Patterns
|
|
403
|
+
|
|
404
|
+
### ❌ What NOT to Do
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
// ❌ NEVER - Early return
|
|
408
|
+
if (isLoading) {
|
|
409
|
+
return <CircularProgress />;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ❌ NEVER - Conditional rendering
|
|
413
|
+
{isLoading ? <Spinner /> : <Content />}
|
|
414
|
+
|
|
415
|
+
// ❌ NEVER - Layout changes
|
|
416
|
+
if (isLoading) {
|
|
417
|
+
return (
|
|
418
|
+
<Box sx={{ height: 100 }}>
|
|
419
|
+
<Spinner />
|
|
420
|
+
</Box>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
return (
|
|
424
|
+
<Box sx={{ height: 500 }}> // Different height!
|
|
425
|
+
<Content />
|
|
426
|
+
</Box>
|
|
427
|
+
);
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### ✅ What TO Do
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
// ✅ BEST - useSuspenseQuery + SuspenseLoader
|
|
434
|
+
<SuspenseLoader>
|
|
435
|
+
<ComponentWithSuspenseQuery />
|
|
436
|
+
</SuspenseLoader>
|
|
437
|
+
|
|
438
|
+
// ✅ ACCEPTABLE - LoadingOverlay
|
|
439
|
+
<LoadingOverlay loading={isLoading}>
|
|
440
|
+
<Content />
|
|
441
|
+
</LoadingOverlay>
|
|
442
|
+
|
|
443
|
+
// ✅ OK - Inline skeleton with same layout
|
|
444
|
+
<Box sx={{ height: 500 }}>
|
|
445
|
+
{isLoading ? <Skeleton variant='rectangular' height='100%' /> : <Content />}
|
|
446
|
+
</Box>
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## Skeleton Loading (Alternative)
|
|
452
|
+
|
|
453
|
+
### MUI Skeleton Component
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { Skeleton, Box } from '@mui/material';
|
|
457
|
+
|
|
458
|
+
export const MyComponent: React.FC = () => {
|
|
459
|
+
const { data, isLoading } = useQuery({ ... });
|
|
460
|
+
|
|
461
|
+
return (
|
|
462
|
+
<Box sx={{ p: 2 }}>
|
|
463
|
+
{isLoading ? (
|
|
464
|
+
<>
|
|
465
|
+
<Skeleton variant='text' width={200} height={40} />
|
|
466
|
+
<Skeleton variant='rectangular' width='100%' height={200} />
|
|
467
|
+
<Skeleton variant='text' width='100%' />
|
|
468
|
+
</>
|
|
469
|
+
) : (
|
|
470
|
+
<>
|
|
471
|
+
<Typography variant='h5'>{data.title}</Typography>
|
|
472
|
+
<img src={data.image} />
|
|
473
|
+
<Typography>{data.description}</Typography>
|
|
474
|
+
</>
|
|
475
|
+
)}
|
|
476
|
+
</Box>
|
|
477
|
+
);
|
|
478
|
+
};
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**Key**: Skeleton must have **same layout** as actual content (no shift)
|
|
482
|
+
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
## Summary
|
|
486
|
+
|
|
487
|
+
**Loading States:**
|
|
488
|
+
- ✅ **PREFERRED**: SuspenseLoader + useSuspenseQuery (modern pattern)
|
|
489
|
+
- ✅ **ACCEPTABLE**: LoadingOverlay (legacy pattern)
|
|
490
|
+
- ✅ **OK**: Skeleton with same layout
|
|
491
|
+
- ❌ **NEVER**: Early returns or conditional layout
|
|
492
|
+
|
|
493
|
+
**Error Handling:**
|
|
494
|
+
- ✅ **ALWAYS**: useMuiSnackbar for user feedback
|
|
495
|
+
- ❌ **NEVER**: react-toastify
|
|
496
|
+
- ✅ Use onError callbacks in queries/mutations
|
|
497
|
+
- ✅ Error boundaries for component-level errors
|
|
498
|
+
|
|
499
|
+
**See Also:**
|
|
500
|
+
- [component-patterns.md](component-patterns.md) - Suspense integration
|
|
501
|
+
- [data-fetching.md](data-fetching.md) - useSuspenseQuery details
|