turash/bugulma/frontend/ASYNC_RENDERING_GUIDE.md
Damir Mukimov 6347f42e20
Consolidate repositories: Remove nested frontend .git and merge into main repository
- Remove nested git repository from bugulma/frontend/.git
- Add all frontend files to main repository tracking
- Convert from separate frontend/backend repos to unified monorepo
- Preserve all frontend code and development history as tracked files
- Eliminate nested repository complexity for simpler development workflow

This creates a proper monorepo structure with frontend and backend
coexisting in the same repository for easier development and deployment.
2025-11-25 06:02:57 +01:00

237 lines
6.6 KiB
Markdown

# Async Rendering Guide
## Overview
All pages and components are designed to render **asynchronously** - they never block on API requests. Components render immediately with loading states or placeholder data, and data updates asynchronously as it arrives from the backend.
## Key Principles
### 1. **Non-Blocking Rendering**
- Components render immediately, even before API data arrives
- Use `placeholderData` in React Query hooks to provide safe defaults
- Never wait for API responses before rendering
### 2. **Progressive Data Loading**
- Initial render shows loading states or empty states
- Data updates asynchronously as API responses arrive
- Multiple API calls happen in parallel, not sequentially
### 3. **Safe Defaults**
- All hooks return safe default structures (empty arrays, undefined, etc.)
- Components handle `undefined` and `null` gracefully
- No assumptions about data structure
## Implementation Patterns
### React Query Hooks with placeholderData
All `useQuery` hooks include `placeholderData` to prevent blocking:
```typescript
// ✅ Good - Renders immediately with empty array
export function useOrganizations() {
return useQuery({
queryKey: organizationKeys.lists(),
queryFn: getOrganizations,
placeholderData: [], // Component renders immediately
staleTime: 30 * 1000,
});
}
// ✅ Good - Renders immediately with undefined
export function useOrganization(id: string | null | undefined) {
return useQuery({
queryKey: organizationKeys.detail(id!),
queryFn: () => getOrganizationById(id!),
enabled: !!id,
placeholderData: undefined, // Component handles undefined
});
}
```
### Component Pattern
Components should handle placeholder data gracefully:
```typescript
// ✅ Good - Handles placeholder data
const MyComponent = () => {
const { data, isLoading } = useOrganizations();
// placeholderData ensures data is always an array
const organizations = Array.isArray(data) ? data : [];
// Render immediately, show loading only if no placeholder data
if (isLoading && organizations.length === 0) {
return <LoadingSpinner />;
}
return <div>{/* Render with data */}</div>;
};
```
### Safe Array Operations
Always check arrays before operations:
```typescript
// ✅ Good - Safe array operations
const items = Array.isArray(data?.items) ? data.items : [];
const filtered = items.filter(item => item?.id);
const mapped = items.map(item => ({ ...item }));
// ❌ Bad - Assumes data is always an array
const items = data.items; // Could be undefined
items.filter(...); // Crashes if undefined
```
## Hooks Updated
All API hooks now include `placeholderData`:
### Organizations
- `useOrganizations()` - `placeholderData: []`
- `useOrganization(id)` - `placeholderData: undefined`
- `useUserOrganizations()` - `placeholderData: []`
### Sites
- `useSite(id)` - `placeholderData: undefined`
- `useSitesByOrganization(id)` - `placeholderData: []`
- `useNearbySites(query)` - `placeholderData: []`
### Resource Flows
- `useResourceFlow(id)` - `placeholderData: undefined`
- `useResourceFlowsBySite(id)` - `placeholderData: []`
- `useResourceFlowsByOrganization(id)` - `placeholderData: []`
### Proposals
- `useProposals()` - `placeholderData: { proposals: [] }`
- `useProposal(id)` - `placeholderData: undefined`
- `useProposalsForOrganization(id)` - `placeholderData: { incoming: [], outgoing: [] }`
### Matching
- `useDirectSymbiosis(id)` - `placeholderData: { providers: [], consumers: [] }`
- `useFindMatches(id)` - `placeholderData: { matches: [] }`
### Analytics
- `useConnectionStatistics()` - `placeholderData: { total_connections: 0 }`
- `useSupplyDemandAnalysis()` - `placeholderData: { supply: [], demand: [] }`
- `useDashboardStatistics()` - `placeholderData: { total_organizations: 0, recent_activity: [] }`
## Benefits
1. **Instant Rendering**: Pages render immediately, no waiting for APIs
2. **Better UX**: Users see loading states or empty states right away
3. **No Freezing**: Components never block on API requests
4. **Progressive Enhancement**: Data appears as it loads
5. **Error Resilience**: Safe defaults prevent crashes
## Testing
To verify async rendering:
1. **Network Throttling**: Use DevTools to throttle network to "Slow 3G"
2. **Check Rendering**: Page should render immediately with loading states
3. **Verify Updates**: Data should appear as API responses arrive
4. **Test Empty States**: Disable network to see empty state handling
## Common Patterns
### Pattern 1: List with Loading State
```typescript
const { data, isLoading } = useOrganizations();
const items = Array.isArray(data) ? data : [];
if (isLoading && items.length === 0) {
return <LoadingSpinner />;
}
return <List items={items} />;
```
### Pattern 2: Detail with Placeholder
```typescript
const { data, isLoading } = useOrganization(id);
if (isLoading && !data) {
return <Skeleton />;
}
if (!data) {
return <NotFound />;
}
return <Detail data={data} />;
```
### Pattern 3: Parallel Loading
```typescript
// Multiple queries load in parallel
const { data: org } = useOrganization(id);
const { data: sites } = useSitesByOrganization(id);
const { data: flows } = useResourceFlowsByOrganization(id);
// All render immediately with placeholderData
// Updates happen asynchronously as data arrives
```
## Anti-Patterns to Avoid
### ❌ Blocking on Data
```typescript
// Bad - Blocks render until data arrives
const { data } = useOrganizations();
if (!data) return null; // Blocks render
```
### ❌ Unsafe Array Operations
```typescript
// Bad - Crashes if data is undefined
const items = data.items;
items.map(...); // Error if items is undefined
```
### ❌ Synchronous Operations in Render
```typescript
// Bad - Blocks render
const processed = heavyComputation(data); // Blocks
```
## Performance Considerations
1. **placeholderData** is lightweight - just empty arrays/objects
2. **React Query** handles caching and deduplication
3. **Components** re-render only when data actually changes
4. **Memoization** prevents unnecessary recalculations
## Migration Checklist
- [x] All `useQuery` hooks have `placeholderData`
- [x] Components handle `undefined` and `null` safely
- [x] Array operations are guarded with `Array.isArray()`
- [x] Loading states show only when no placeholder data exists
- [x] Error states don't block rendering
- [x] Context providers don't block on data
## Future Improvements
1. **Suspense Boundaries**: Consider React Suspense for better loading UX
2. **Optimistic Updates**: Update UI immediately, rollback on error
3. **Streaming**: For large datasets, consider streaming responses
4. **Prefetching**: Prefetch data before navigation