error and load state
This commit is contained in:
264
apps/website/docs/STREAMLINED_STATE_HANDLING_SUMMARY.md
Normal file
264
apps/website/docs/STREAMLINED_STATE_HANDLING_SUMMARY.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Streamlined Error & Load State Handling - Quick Reference
|
||||
|
||||
## 🎯 Goal
|
||||
Standardize error and loading state handling across all GridPilot website pages using user-friendly, accessible, and consistent shared components.
|
||||
|
||||
## 📁 File Structure
|
||||
|
||||
```
|
||||
apps/website/components/shared/
|
||||
├── state/
|
||||
│ ├── LoadingWrapper.tsx # Loading states
|
||||
│ ├── ErrorDisplay.tsx # Error states
|
||||
│ ├── EmptyState.tsx # Empty states
|
||||
│ └── StateContainer.tsx # Combined wrapper
|
||||
├── hooks/
|
||||
│ └── useDataFetching.ts # Unified data fetching
|
||||
└── types/
|
||||
└── state.types.ts # TypeScript interfaces
|
||||
```
|
||||
|
||||
## 🚀 Quick Start Examples
|
||||
|
||||
### 1. Basic Page Implementation
|
||||
|
||||
```typescript
|
||||
import { useDataFetching } from '@/components/shared/hooks/useDataFetching';
|
||||
import { LoadingWrapper } from '@/components/shared/state/LoadingWrapper';
|
||||
import { ErrorDisplay } from '@/components/shared/state/ErrorDisplay';
|
||||
import { EmptyState } from '@/components/shared/state/EmptyState';
|
||||
|
||||
function MyPage() {
|
||||
const { data, isLoading, error, retry } = useDataFetching({
|
||||
queryKey: ['myData'],
|
||||
queryFn: () => myService.getData(),
|
||||
});
|
||||
|
||||
if (isLoading) return <LoadingWrapper variant="full-screen" />;
|
||||
if (error) return <ErrorDisplay error={error} onRetry={retry} variant="full-screen" />;
|
||||
if (!data) return <EmptyState icon={Calendar} title="No data" />;
|
||||
|
||||
return <MyContent data={data} />;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Using StateContainer (Recommended)
|
||||
|
||||
```typescript
|
||||
import { StateContainer } from '@/components/shared/state/StateContainer';
|
||||
|
||||
function MyPage() {
|
||||
const { data, isLoading, error, retry } = useDataFetching({
|
||||
queryKey: ['myData'],
|
||||
queryFn: () => myService.getData(),
|
||||
});
|
||||
|
||||
return (
|
||||
<StateContainer
|
||||
data={data}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
retry={retry}
|
||||
config={{
|
||||
loading: { variant: 'skeleton', message: 'Loading...' },
|
||||
error: { variant: 'full-screen' },
|
||||
empty: {
|
||||
icon: Trophy,
|
||||
title: 'No data found',
|
||||
description: 'Try refreshing the page',
|
||||
action: { label: 'Refresh', onClick: retry }
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(content) => <MyContent data={content} />}
|
||||
</StateContainer>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 Component Variants
|
||||
|
||||
### LoadingWrapper
|
||||
- `spinner` - Traditional spinner (default)
|
||||
- `skeleton` - Skeleton screens
|
||||
- `full-screen` - Centered in viewport
|
||||
- `inline` - Compact inline
|
||||
- `card` - Card placeholders
|
||||
|
||||
### ErrorDisplay
|
||||
- `full-screen` - Full page error
|
||||
- `inline` - Inline error message
|
||||
- `card` - Error card
|
||||
- `toast` - Toast notification
|
||||
|
||||
### EmptyState
|
||||
- `default` - Standard empty state
|
||||
- `minimal` - Simple version
|
||||
- `full-page` - Full page empty state
|
||||
|
||||
## 🔧 useDataFetching Hook
|
||||
|
||||
```typescript
|
||||
const {
|
||||
data, // The fetched data
|
||||
isLoading, // Initial load
|
||||
isFetching, // Any fetch (including refetch)
|
||||
error, // ApiError or null
|
||||
retry, // Retry failed request
|
||||
refetch, // Manual refetch
|
||||
lastUpdated, // Timestamp
|
||||
isStale // Cache status
|
||||
} = useDataFetching({
|
||||
queryKey: ['uniqueKey', id],
|
||||
queryFn: () => apiService.getData(id),
|
||||
enabled: true, // Enable/disable query
|
||||
retryOnMount: true, // Auto-retry on mount
|
||||
cacheTime: 5 * 60 * 1000, // 5 minutes
|
||||
staleTime: 1 * 60 * 1000, // 1 minute
|
||||
maxRetries: 3,
|
||||
retryDelay: 1000,
|
||||
onSuccess: (data) => { /* ... */ },
|
||||
onError: (error) => { /* ... */ },
|
||||
});
|
||||
```
|
||||
|
||||
## 📋 Migration Checklist
|
||||
|
||||
### Phase 1: Foundation
|
||||
- [ ] Create `components/shared/state/` directory
|
||||
- [ ] Implement `LoadingWrapper.tsx`
|
||||
- [ ] Implement `ErrorDisplay.tsx`
|
||||
- [ ] Implement `EmptyState.tsx`
|
||||
- [ ] Implement `StateContainer.tsx`
|
||||
- [ ] Implement `useDataFetching.ts` hook
|
||||
- [ ] Create TypeScript interfaces
|
||||
- [ ] Add comprehensive tests
|
||||
|
||||
### Phase 2: Core Pages (High Priority)
|
||||
- [ ] `app/dashboard/page.tsx`
|
||||
- [ ] `app/leagues/[id]/LeagueDetailInteractive.tsx`
|
||||
- [ ] `app/races/[id]/RaceDetailInteractive.tsx`
|
||||
|
||||
### Phase 3: Additional Pages
|
||||
- [ ] `app/teams/[id]/TeamDetailInteractive.tsx`
|
||||
- [ ] `app/leagues/[id]/schedule/page.tsx`
|
||||
- [ ] `app/races/[id]/results/RaceResultsInteractive.tsx`
|
||||
- [ ] `app/races/[id]/stewarding/RaceStewardingInteractive.tsx`
|
||||
- [ ] Sponsor pages
|
||||
- [ ] Profile pages
|
||||
|
||||
### Phase 4: Cleanup
|
||||
- [ ] Remove old loading components
|
||||
- [ ] Remove old error components
|
||||
- [ ] Update documentation
|
||||
- [ ] Team training
|
||||
|
||||
## 🔄 Before & After
|
||||
|
||||
### Before (Inconsistent)
|
||||
```typescript
|
||||
function DashboardPage() {
|
||||
const { data, isLoading, error } = useDashboardOverview();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<main className="min-h-screen bg-deep-graphite flex items-center justify-center">
|
||||
<div className="text-white">Loading dashboard...</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (error || !dashboardData) {
|
||||
return (
|
||||
<main className="min-h-screen bg-deep-graphite flex items-center justify-center">
|
||||
<div className="text-red-400">Failed to load dashboard</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return <DashboardContent data={dashboardData} />;
|
||||
}
|
||||
```
|
||||
|
||||
### After (Standardized)
|
||||
```typescript
|
||||
function DashboardPage() {
|
||||
const { data, isLoading, error, retry } = useDataFetching({
|
||||
queryKey: ['dashboardOverview'],
|
||||
queryFn: () => dashboardService.getDashboardOverview(),
|
||||
});
|
||||
|
||||
return (
|
||||
<StateContainer
|
||||
data={data}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
retry={retry}
|
||||
config={{
|
||||
loading: { variant: 'full-screen', message: 'Loading dashboard...' },
|
||||
error: { variant: 'full-screen' },
|
||||
empty: {
|
||||
icon: Activity,
|
||||
title: 'No dashboard data',
|
||||
description: 'Try refreshing the page',
|
||||
action: { label: 'Refresh', onClick: retry }
|
||||
}
|
||||
}}
|
||||
>
|
||||
{(content) => <DashboardContent data={content} />}
|
||||
</StateContainer>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 Key Benefits
|
||||
|
||||
1. **Consistency**: Same patterns across all pages
|
||||
2. **User-Friendly**: Clear messages and helpful actions
|
||||
3. **Accessible**: ARIA labels, keyboard navigation
|
||||
4. **Developer-Friendly**: Simple API, less boilerplate
|
||||
5. **Maintainable**: Single source of truth
|
||||
6. **Flexible**: Multiple variants for different needs
|
||||
7. **Type-Safe**: Full TypeScript support
|
||||
|
||||
## 📊 Success Metrics
|
||||
|
||||
- ✅ 100% page coverage
|
||||
- ✅ 60% less error handling code
|
||||
- ✅ Consistent UX across app
|
||||
- ✅ Better error recovery
|
||||
- ✅ Faster development
|
||||
|
||||
## 🔗 Related Files
|
||||
|
||||
- Design Document: `STREAMLINED_STATE_HANDLING_DESIGN.md`
|
||||
- TypeScript Interfaces: `components/shared/state/types.ts`
|
||||
- Component Tests: `components/shared/state/*.test.tsx`
|
||||
- Hook Tests: `components/shared/hooks/useDataFetching.test.ts`
|
||||
|
||||
## 💡 Tips
|
||||
|
||||
1. **Always use `useDataFetching`** instead of manual state management
|
||||
2. **Prefer `StateContainer`** for complex pages
|
||||
3. **Use `skeleton` variant** for better perceived performance
|
||||
4. **Enable `retryOnMount`** for recoverable errors
|
||||
5. **Customize config** per page needs
|
||||
6. **Test all states**: loading, error, empty, success
|
||||
|
||||
## 🆘 Troubleshooting
|
||||
|
||||
**Q: Error not showing?**
|
||||
A: Check if error is instance of `ApiError`
|
||||
|
||||
**Q: Loading not visible?**
|
||||
A: Verify `isLoading` is true and component is rendered
|
||||
|
||||
**Q: Retry not working?**
|
||||
A: Ensure `onRetry` prop is passed to ErrorDisplay
|
||||
|
||||
**Q: Empty state not showing?**
|
||||
A: Check if data is null/undefined and `showEmpty` is true
|
||||
|
||||
---
|
||||
|
||||
For detailed implementation guide, see: `STREAMLINED_STATE_HANDLING_DESIGN.md`
|
||||
Reference in New Issue
Block a user