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

6.6 KiB

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:

// ✅ 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:

// ✅ 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:

// ✅ 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

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

const { data, isLoading } = useOrganization(id);

if (isLoading && !data) {
  return <Skeleton />;
}

if (!data) {
  return <NotFound />;
}

return <Detail data={data} />;

Pattern 3: Parallel Loading

// 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

// Bad - Blocks render until data arrives
const { data } = useOrganizations();
if (!data) return null; // Blocks render

Unsafe Array Operations

// Bad - Crashes if data is undefined
const items = data.items;
items.map(...); // Error if items is undefined

Synchronous Operations in Render

// 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

  • All useQuery hooks have placeholderData
  • Components handle undefined and null safely
  • Array operations are guarded with Array.isArray()
  • Loading states show only when no placeholder data exists
  • Error states don't block rendering
  • 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