Claude Cursor opencode Skill

frontend-dev-guidelines

Frontend development guidelines for React/TypeScript applications. Modern patterns including Suspense, lazy loading, useSuspenseQuery, file organization with features directory, MUI v7 styling, TanStack Router, performance optimization, and TypeScript best practices. Use when cre

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vodailocz-kilo-kit-mcp-skills_engineering_frontend-development-0448e6c.zip · 40 KB
Part of vodailocz/kilo-kit-mcp — 142 skills

Install

skills CLI npx skills add https://github.com/VoDaiLocz/kilo-kit-mcp/tree/main/skills/engineering/frontend-development
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vodailocz-kilo-kit-mcp@llmmart
Git git clone https://github.com/VoDaiLocz/kilo-kit-mcp.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vodailocz/kilo-kit-mcp collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Frontend Development Guidelines

Purpose

Comprehensive guide for modern React development, emphasizing Suspense-based data fetching, lazy loading, proper file organization, and performance optimization.

When to Use This Skill

  • Creating new components or pages
  • Building new features
  • Fetching data with TanStack Query
  • Setting up routing with TanStack Router
  • Styling components with MUI v7
  • Performance optimization
  • Organizing frontend code
  • TypeScript best practices

Quick Start

New Component Checklist

Creating a component? Follow this checklist:

  • Use React.FC<Props> pattern with TypeScript
  • Lazy load if heavy component: React.lazy(() => import())
  • Wrap in <SuspenseLoader> for loading states
  • Use useSuspenseQuery for data fetching
  • Import aliases: @/, ~types, ~components, ~features
  • Styles: Inline if <100 lines, separate file if >100 lines
  • Use useCallback for event handlers passed to children
  • Default export at bottom
  • No early returns with loading spinners
  • Use useMuiSnackbar for user notifications

New Feature Checklist

Creating a feature? Set up this structure:

  • Create features/{feature-name}/ directory
  • Create subdirectories: api/, components/, hooks/, helpers/, types/
  • Create API service file: api/{feature}Api.ts
  • Set up TypeScript types in types/
  • Create route in routes/{feature-name}/index.tsx
  • Lazy load feature components
  • Use Suspense boundaries
  • Export public API from feature index.ts

Import Aliases Quick Reference

Alias Resolves To Example
@/ src/ import { apiClient } from '@/lib/apiClient'
~types src/types import type { User } from '~types/user'
~components src/components import { SuspenseLoader } from '~components/SuspenseLoader'
~features src/features import { authApi } from '~features/auth'

Defined in: vite.config.ts lines 180-185


Common Imports Cheatsheet

// React & Lazy Loading
import React, { useState, useCallback, useMemo } from 'react';
const Heavy = React.lazy(() => import('./Heavy'));

// MUI Components
import { Box, Paper, Typography, Button, Grid } from '@mui/material';
import type { SxProps, Theme } from '@mui/material';

// TanStack Query (Suspense)
import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query';

// TanStack Router
import { createFileRoute } from '@tanstack/react-router';

// Project Components
import { SuspenseLoader } from '~components/SuspenseLoader';

// Hooks
import { useAuth } from '@/hooks/useAuth';
import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';

// Types
import type { Post } from '~types/post';

Topic Guides

🎨 Component Patterns

Modern React components use:

  • React.FC<Props> for type safety
  • React.lazy() for code splitting
  • SuspenseLoader for loading states
  • Named const + default export pattern

Key Concepts:

  • Lazy load heavy components (DataGrid, charts, editors)
  • Always wrap lazy components in Suspense
  • Use SuspenseLoader component (with fade animation)
  • Component structure: Props → Hooks → Handlers → Render → Export

CRITICAL RULE: UI RESEARCH HARD-GATE Before creating any user-facing component or page, you MUST run a Subagent to research real-world UI patterns.

  • Call invoke_subagent (research) with instructions to search Awwwards/ThreeUI for specific context keywords (e.g., "SaaS Dashboard").
  • Do not invent color palettes or layouts blindly. Use the exact Tailwind/MUI theme variables returned by the Subagent's visual analysis report.

📖 Complete Guide: resources/component-patterns.md


📊 Data Fetching

PRIMARY PATTERN: useSuspenseQuery

  • Use with Suspense boundaries
  • Cache-first strategy (check grid cache before API)
  • Replaces isLoading checks
  • Type-safe with generics

API Service Layer:

  • Create features/{feature}/api/{feature}Api.ts
  • Use apiClient axios instance
  • Centralized methods per feature
  • Route format: /form/route (NOT /api/form/route)

📖 Complete Guide: resources/data-fetching.md


📁 File Organization

features/ vs components/:

  • features/: Domain-specific (posts, comments, auth)
  • components/: Truly reusable (SuspenseLoader, CustomAppBar)

Feature Subdirectories:

features/
  my-feature/
    api/          # API service layer
    components/   # Feature components
    hooks/        # Custom hooks
    helpers/      # Utility functions
    types/        # TypeScript types

📖 Complete Guide: resources/file-organization.md


🎨 Styling

Inline vs Separate:

  • <100 lines: Inline const styles: Record<string, SxProps<Theme>>
  • 100 lines: Separate .styles.ts file

Primary Method:

  • Use sx prop for MUI components
  • Type-safe with SxProps<Theme>
  • Theme access: (theme) => theme.palette.primary.main

MUI v7 Grid:

<Grid size={{ xs: 12, md: 6 }}>  // ✅ v7 syntax
<Grid xs={12} md={6}>             // ❌ Old syntax

📖 Complete Guide: resources/styling-guide.md


🛣️ Routing

TanStack Router - Folder-Based:

  • Directory: routes/my-route/index.tsx
  • Lazy load components
  • Use createFileRoute
  • Breadcrumb data in loader

Example:

import { createFileRoute } from '@tanstack/react-router';
import { lazy } from 'react';

const MyPage = lazy(() => import('@/features/my-feature/components/MyPage'));

export const Route = createFileRoute('/my-route/')({
    component: MyPage,
    loader: () => ({ crumb: 'My Route' }),
});

📖 Complete Guide: resources/routing-guide.md


⏳ Loading & Error States

CRITICAL RULE: No Early Returns

// ❌ NEVER - Causes layout shift
if (isLoading) {
    return <LoadingSpinner />;
}

// ✅ ALWAYS - Consistent layout
<SuspenseLoader>
    <Content />
</SuspenseLoader>

Why: Prevents Cumulative Layout Shift (CLS), better UX

Error Handling:

  • Use useMuiSnackbar for user feedback
  • NEVER react-toastify
  • TanStack Query onError callbacks

📖 Complete Guide: resources/loading-and-error-states.md


⚡ Performance

Optimization Patterns:

  • useMemo: Expensive computations (filter, sort, map)
  • useCallback: Event handlers passed to children
  • React.memo: Expensive components
  • Debounced search (300-500ms)
  • Memory leak prevention (cleanup in useEffect)

📖 Complete Guide: resources/performance.md


📘 TypeScript

Standards:

  • Strict mode, no any type
  • Explicit return types on functions
  • Type imports: import type { User } from '~types/user'
  • Component prop interfaces with JSDoc

📖 Complete Guide: resources/typescript-standards.md


🔧 Common Patterns

Covered Topics:

  • React Hook Form with Zod validation
  • DataGrid wrapper contracts
  • Dialog component standards
  • useAuth hook for current user
  • Mutation patterns with cache invalidation

📖 Complete Guide: resources/common-patterns.md


📚 Complete Examples

Full working examples:

  • Modern component with all patterns
  • Complete feature structure
  • API service layer
  • Route with lazy loading
  • Suspense + useSuspenseQuery
  • Form with validation

📖 Complete Guide: resources/complete-examples.md


Navigation Guide

Need to... Read this resource
Create a component component-patterns.md
Fetch data data-fetching.md
Organize files/folders file-organization.md
Style components styling-guide.md
Set up routing routing-guide.md
Handle loading/errors loading-and-error-states.md
Optimize performance performance.md
TypeScript types typescript-standards.md
Forms/Auth/DataGrid common-patterns.md
See full examples complete-examples.md

Core Principles

  1. Lazy Load Everything Heavy: Routes, DataGrid, charts, editors
  2. Suspense for Loading: Use SuspenseLoader, not early returns
  3. useSuspenseQuery: Primary data fetching pattern for new code
  4. Features are Organized: api/, components/, hooks/, helpers/ subdirs
  5. Styles Based on Size: <100 inline, >100 separate
  6. Import Aliases: Use @/, ~types, ~components, ~features
  7. No Early Returns: Prevents layout shift
  8. useMuiSnackbar: For all user notifications

Quick Reference: File Structure

src/
  features/
    my-feature/
      api/
        myFeatureApi.ts       # API service
      components/
        MyFeature.tsx         # Main component
        SubComponent.tsx      # Related components
      hooks/
        useMyFeature.ts       # Custom hooks
        useSuspenseMyFeature.ts  # Suspense hooks
      helpers/
        myFeatureHelpers.ts   # Utilities
      types/
        index.ts              # TypeScript types
      index.ts                # Public exports

  components/
    SuspenseLoader/
      SuspenseLoader.tsx      # Reusable loader
    CustomAppBar/
      CustomAppBar.tsx        # Reusable app bar

  routes/
    my-route/
      index.tsx               # Route component
      create/
        index.tsx             # Nested route

Modern Component Template (Quick Copy)

import React, { useState, useCallback } from 'react';
import { Box, Paper } from '@mui/material';
import { useSuspenseQuery } from '@tanstack/react-query';
import { featureApi } from '../api/featureApi';
import type { FeatureData } from '~types/feature';

interface MyComponentProps {
    id: number;
    onAction?: () => void;
}

export const MyComponent: React.FC<MyComponentProps> = ({ id, onAction }) => {
    const [state, setState] = useState<string>('');

    const { data } = useSuspenseQuery({
        queryKey: ['feature', id],
        queryFn: () => featureApi.getFeature(id),
    });

    const handleAction = useCallback(() => {
        setState('updated');
        onAction?.();
    }, [onAction]);

    return (
        <Box sx={{ p: 2 }}>
            <Paper sx={{ p: 3 }}>
                {/* Content */}
            </Paper>
        </Box>
    );
};

export default MyComponent;

For complete examples, see resources/complete-examples.md


Related Skills

  • error-tracking: Error tracking with Sentry (applies to frontend too)
  • backend-dev-guidelines: Backend API patterns that frontend consumes

Skill Status: Modular structure with progressive loading for optimal context management

Files (kilo-kit-mcp)
  • resources
    • common-patterns.md 8.2 KB
      # Common Patterns
      
      Frequently used patterns for forms, authentication, DataGrid, dialogs, and other common UI elements.
      
      ---
      
      ## Authentication with useAuth
      
      ### Getting Current User
      
      ```typescript
      import { useAuth } from '@/hooks/useAuth';
      
      export const MyComponent: React.FC = () => {
          const { user } = useAuth();
      
          // Available properties:
          // - user.id: string
          // - user.email: string
          // - user.username: string
          // - user.roles: string[]
      
          return (
              <div>
                  <p>Logged in as: {user.email}</p>
                  <p>Username: {user.username}</p>
                  <p>Roles: {user.roles.join(', ')}</p>
              </div>
          );
      };
      ```
      
      **NEVER make direct API calls for auth** - always use `useAuth` hook.
      
      ---
      
      ## Forms with React Hook Form
      
      ### Basic Form
      
      ```typescript
      import { useForm } from 'react-hook-form';
      import { zodResolver } from '@hookform/resolvers/zod';
      import { z } from 'zod';
      import { TextField, Button } from '@mui/material';
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      
      // Zod schema for validation
      const formSchema = z.object({
          username: z.string().min(3, 'Username must be at least 3 characters'),
          email: z.string().email('Invalid email address'),
          age: z.number().min(18, 'Must be 18 or older'),
      });
      
      type FormData = z.infer<typeof formSchema>;
      
      export const MyForm: React.FC = () => {
          const { showSuccess, showError } = useMuiSnackbar();
      
          const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
              resolver: zodResolver(formSchema),
              defaultValues: {
                  username: '',
                  email: '',
                  age: 18,
              },
          });
      
          const onSubmit = async (data: FormData) => {
              try {
                  await api.submitForm(data);
                  showSuccess('Form submitted successfully');
              } catch (error) {
                  showError('Failed to submit form');
              }
          };
      
          return (
              <form onSubmit={handleSubmit(onSubmit)}>
                  <TextField
                      {...register('username')}
                      label='Username'
                      error={!!errors.username}
                      helperText={errors.username?.message}
                  />
      
                  <TextField
                      {...register('email')}
                      label='Email'
                      error={!!errors.email}
                      helperText={errors.email?.message}
                      type='email'
                  />
      
                  <TextField
                      {...register('age', { valueAsNumber: true })}
                      label='Age'
                      error={!!errors.age}
                      helperText={errors.age?.message}
                      type='number'
                  />
      
                  <Button type='submit' variant='contained'>
                      Submit
                  </Button>
              </form>
          );
      };
      ```
      
      ---
      
      ## Dialog Component Pattern
      
      ### Standard Dialog Structure
      
      From BEST_PRACTICES.md - All dialogs should have:
      - Icon in title
      - Close button (X)
      - Action buttons at bottom
      
      ```typescript
      import { Dialog, DialogTitle, DialogContent, DialogActions, Button, IconButton } from '@mui/material';
      import { Close, Info } from '@mui/icons-material';
      
      interface MyDialogProps {
          open: boolean;
          onClose: () => void;
          onConfirm: () => void;
      }
      
      export const MyDialog: React.FC<MyDialogProps> = ({ open, onClose, onConfirm }) => {
          return (
              <Dialog open={open} onClose={onClose} maxWidth='sm' fullWidth>
                  <DialogTitle>
                      <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                          <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
                              <Info color='primary' />
                              Dialog Title
                          </Box>
                          <IconButton onClick={onClose} size='small'>
                              <Close />
                          </IconButton>
                      </Box>
                  </DialogTitle>
      
                  <DialogContent>
                      {/* Content here */}
                  </DialogContent>
      
                  <DialogActions>
                      <Button onClick={onClose}>Cancel</Button>
                      <Button onClick={onConfirm} variant='contained'>
                          Confirm
                      </Button>
                  </DialogActions>
              </Dialog>
          );
      };
      ```
      
      ---
      
      ## DataGrid Wrapper Pattern
      
      ### Wrapper Component Contract
      
      From BEST_PRACTICES.md - DataGrid wrappers should accept:
      
      **Required Props:**
      - `rows`: Data array
      - `columns`: Column definitions
      - Loading/error states
      
      **Optional Props:**
      - Toolbar components
      - Custom actions
      - Initial state
      
      ```typescript
      import { DataGridPro } from '@mui/x-data-grid-pro';
      import type { GridColDef } from '@mui/x-data-grid-pro';
      
      interface DataGridWrapperProps {
          rows: any[];
          columns: GridColDef[];
          loading?: boolean;
          toolbar?: React.ReactNode;
          onRowClick?: (row: any) => void;
      }
      
      export const DataGridWrapper: React.FC<DataGridWrapperProps> = ({
          rows,
          columns,
          loading = false,
          toolbar,
          onRowClick,
      }) => {
          return (
              <DataGridPro
                  rows={rows}
                  columns={columns}
                  loading={loading}
                  slots={{ toolbar: toolbar ? () => toolbar : undefined }}
                  onRowClick={(params) => onRowClick?.(params.row)}
                  // Standard configuration
                  pagination
                  pageSizeOptions={[25, 50, 100]}
                  initialState={{
                      pagination: { paginationModel: { pageSize: 25 } },
                  }}
              />
          );
      };
      ```
      
      ---
      
      ## Mutation Patterns
      
      ### Update with Cache Invalidation
      
      ```typescript
      import { useMutation, useQueryClient } from '@tanstack/react-query';
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      
      export const useUpdateEntity = () => {
          const queryClient = useQueryClient();
          const { showSuccess, showError } = useMuiSnackbar();
      
          return useMutation({
              mutationFn: ({ id, data }: { id: number; data: any }) =>
                  api.updateEntity(id, data),
      
              onSuccess: (result, variables) => {
                  // Invalidate affected queries
                  queryClient.invalidateQueries({ queryKey: ['entity', variables.id] });
                  queryClient.invalidateQueries({ queryKey: ['entities'] });
      
                  showSuccess('Entity updated');
              },
      
              onError: () => {
                  showError('Failed to update entity');
              },
          });
      };
      
      // Usage
      const updateEntity = useUpdateEntity();
      
      const handleSave = () => {
          updateEntity.mutate({ id: 123, data: { name: 'New Name' } });
      };
      ```
      
      ---
      
      ## State Management Patterns
      
      ### TanStack Query for Server State (PRIMARY)
      
      Use TanStack Query for **all server data**:
      - Fetching: useSuspenseQuery
      - Mutations: useMutation
      - Caching: Automatic
      - Synchronization: Built-in
      
      ```typescript
      // ✅ CORRECT - TanStack Query for server data
      const { data: users } = useSuspenseQuery({
          queryKey: ['users'],
          queryFn: () => userApi.getUsers(),
      });
      ```
      
      ### useState for UI State
      
      Use `useState` for **local UI state only**:
      - Form inputs (uncontrolled)
      - Modal open/closed
      - Selected tab
      - Temporary UI flags
      
      ```typescript
      // ✅ CORRECT - useState for UI state
      const [modalOpen, setModalOpen] = useState(false);
      const [selectedTab, setSelectedTab] = useState(0);
      ```
      
      ### Zustand for Global Client State (Minimal)
      
      Use Zustand only for **global client state**:
      - Theme preference
      - Sidebar collapsed state
      - User preferences (not from server)
      
      ```typescript
      import { create } from 'zustand';
      
      interface AppState {
          sidebarOpen: boolean;
          toggleSidebar: () => void;
      }
      
      export const useAppState = create<AppState>((set) => ({
          sidebarOpen: true,
          toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
      }));
      ```
      
      **Avoid prop drilling** - use context or Zustand instead.
      
      ---
      
      ## Summary
      
      **Common Patterns:**
      - ✅ useAuth hook for current user (id, email, roles, username)
      - ✅ React Hook Form + Zod for forms
      - ✅ Dialog with icon + close button
      - ✅ DataGrid wrapper contracts
      - ✅ Mutations with cache invalidation
      - ✅ TanStack Query for server state
      - ✅ useState for UI state
      - ✅ Zustand for global client state (minimal)
      
      **See Also:**
      - [data-fetching.md](data-fetching.md) - TanStack Query patterns
      - [component-patterns.md](component-patterns.md) - Component structure
      - [loading-and-error-states.md](loading-and-error-states.md) - Error handling
    • complete-examples.md 23.9 KB
      # Complete Examples
      
      Full working examples combining all modern patterns: React.FC, lazy loading, Suspense, useSuspenseQuery, styling, routing, and error handling.
      
      ---
      
      ## Example 1: Complete Modern Component
      
      Combines: React.FC, useSuspenseQuery, cache-first, useCallback, styling, error handling
      
      ```typescript
      /**
       * User profile display component
       * Demonstrates modern patterns with Suspense and TanStack Query
       */
      import React, { useState, useCallback, useMemo } from 'react';
      import { Box, Paper, Typography, Button, Avatar } from '@mui/material';
      import type { SxProps, Theme } from '@mui/material';
      import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query';
      import { userApi } from '../api/userApi';
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      import type { User } from '~types/user';
      
      // Styles object
      const componentStyles: Record<string, SxProps<Theme>> = {
          container: {
              p: 3,
              maxWidth: 600,
              margin: '0 auto',
          },
          header: {
              display: 'flex',
              alignItems: 'center',
              gap: 2,
              mb: 3,
          },
          content: {
              display: 'flex',
              flexDirection: 'column',
              gap: 2,
          },
          actions: {
              display: 'flex',
              gap: 1,
              mt: 2,
          },
      };
      
      interface UserProfileProps {
          userId: string;
          onUpdate?: () => void;
      }
      
      export const UserProfile: React.FC<UserProfileProps> = ({ userId, onUpdate }) => {
          const queryClient = useQueryClient();
          const { showSuccess, showError } = useMuiSnackbar();
          const [isEditing, setIsEditing] = useState(false);
      
          // Suspense query - no isLoading needed!
          const { data: user } = useSuspenseQuery({
              queryKey: ['user', userId],
              queryFn: () => userApi.getUser(userId),
              staleTime: 5 * 60 * 1000,
          });
      
          // Update mutation
          const updateMutation = useMutation({
              mutationFn: (updates: Partial<User>) =>
                  userApi.updateUser(userId, updates),
      
              onSuccess: () => {
                  queryClient.invalidateQueries({ queryKey: ['user', userId] });
                  showSuccess('Profile updated');
                  setIsEditing(false);
                  onUpdate?.();
              },
      
              onError: () => {
                  showError('Failed to update profile');
              },
          });
      
          // Memoized computed value
          const fullName = useMemo(() => {
              return `${user.firstName} ${user.lastName}`;
          }, [user.firstName, user.lastName]);
      
          // Event handlers with useCallback
          const handleEdit = useCallback(() => {
              setIsEditing(true);
          }, []);
      
          const handleSave = useCallback(() => {
              updateMutation.mutate({
                  firstName: user.firstName,
                  lastName: user.lastName,
              });
          }, [user, updateMutation]);
      
          const handleCancel = useCallback(() => {
              setIsEditing(false);
          }, []);
      
          return (
              <Paper sx={componentStyles.container}>
                  <Box sx={componentStyles.header}>
                      <Avatar sx={{ width: 64, height: 64 }}>
                          {user.firstName[0]}{user.lastName[0]}
                      </Avatar>
                      <Box>
                          <Typography variant='h5'>{fullName}</Typography>
                          <Typography color='text.secondary'>{user.email}</Typography>
                      </Box>
                  </Box>
      
                  <Box sx={componentStyles.content}>
                      <Typography>Username: {user.username}</Typography>
                      <Typography>Roles: {user.roles.join(', ')}</Typography>
                  </Box>
      
                  <Box sx={componentStyles.actions}>
                      {!isEditing ? (
                          <Button variant='contained' onClick={handleEdit}>
                              Edit Profile
                          </Button>
                      ) : (
                          <>
                              <Button
                                  variant='contained'
                                  onClick={handleSave}
                                  disabled={updateMutation.isPending}
                              >
                                  {updateMutation.isPending ? 'Saving...' : 'Save'}
                              </Button>
                              <Button onClick={handleCancel}>
                                  Cancel
                              </Button>
                          </>
                      )}
                  </Box>
              </Paper>
          );
      };
      
      export default UserProfile;
      ```
      
      **Usage:**
      ```typescript
      <SuspenseLoader>
          <UserProfile userId='123' onUpdate={() => console.log('Updated')} />
      </SuspenseLoader>
      ```
      
      ---
      
      ## Example 2: Complete Feature Structure
      
      Real example based on `features/posts/`:
      
      ```
      features/
        users/
          api/
            userApi.ts                # API service layer
          components/
            UserProfile.tsx           # Main component (from Example 1)
            UserList.tsx              # List component
            UserBlog.tsx              # Blog component
            modals/
              DeleteUserModal.tsx     # Modal component
          hooks/
            useSuspenseUser.ts        # Suspense query hook
            useUserMutations.ts       # Mutation hooks
            useUserPermissions.ts     # Feature-specific hook
          helpers/
            userHelpers.ts            # Utility functions
            validation.ts             # Validation logic
          types/
            index.ts                  # TypeScript interfaces
          index.ts                    # Public API exports
      ```
      
      ### API Service (userApi.ts)
      
      ```typescript
      import apiClient from '@/lib/apiClient';
      import type { User, CreateUserPayload, UpdateUserPayload } from '../types';
      
      export const userApi = {
          getUser: async (userId: string): Promise<User> => {
              const { data } = await apiClient.get(`/users/${userId}`);
              return data;
          },
      
          getUsers: async (): Promise<User[]> => {
              const { data } = await apiClient.get('/users');
              return data;
          },
      
          createUser: async (payload: CreateUserPayload): Promise<User> => {
              const { data } = await apiClient.post('/users', payload);
              return data;
          },
      
          updateUser: async (userId: string, payload: UpdateUserPayload): Promise<User> => {
              const { data } = await apiClient.put(`/users/${userId}`, payload);
              return data;
          },
      
          deleteUser: async (userId: string): Promise<void> => {
              await apiClient.delete(`/users/${userId}`);
          },
      };
      ```
      
      ### Suspense Hook (useSuspenseUser.ts)
      
      ```typescript
      import { useSuspenseQuery } from '@tanstack/react-query';
      import { userApi } from '../api/userApi';
      import type { User } from '../types';
      
      export function useSuspenseUser(userId: string) {
          return useSuspenseQuery<User, Error>({
              queryKey: ['user', userId],
              queryFn: () => userApi.getUser(userId),
              staleTime: 5 * 60 * 1000,
              gcTime: 10 * 60 * 1000,
          });
      }
      
      export function useSuspenseUsers() {
          return useSuspenseQuery<User[], Error>({
              queryKey: ['users'],
              queryFn: () => userApi.getUsers(),
              staleTime: 1 * 60 * 1000,  // Shorter for list
          });
      }
      ```
      
      ### Types (types/index.ts)
      
      ```typescript
      export interface User {
          id: string;
          username: string;
          email: string;
          firstName: string;
          lastName: string;
          roles: string[];
          createdAt: string;
          updatedAt: string;
      }
      
      export interface CreateUserPayload {
          username: string;
          email: string;
          firstName: string;
          lastName: string;
          password: string;
      }
      
      export type UpdateUserPayload = Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>;
      ```
      
      ### Public Exports (index.ts)
      
      ```typescript
      // Export components
      export { UserProfile } from './components/UserProfile';
      export { UserList } from './components/UserList';
      
      // Export hooks
      export { useSuspenseUser, useSuspenseUsers } from './hooks/useSuspenseUser';
      export { useUserMutations } from './hooks/useUserMutations';
      
      // Export API
      export { userApi } from './api/userApi';
      
      // Export types
      export type { User, CreateUserPayload, UpdateUserPayload } from './types';
      ```
      
      ---
      
      ## Example 3: Complete Route with Lazy Loading
      
      ```typescript
      /**
       * User profile route
       * Path: /users/:userId
       */
      
      import { createFileRoute } from '@tanstack/react-router';
      import { lazy } from 'react';
      import { SuspenseLoader } from '~components/SuspenseLoader';
      
      // Lazy load the UserProfile component
      const UserProfile = lazy(() =>
          import('@/features/users/components/UserProfile').then(
              (module) => ({ default: module.UserProfile })
          )
      );
      
      export const Route = createFileRoute('/users/$userId')({
          component: UserProfilePage,
          loader: ({ params }) => ({
              crumb: `User ${params.userId}`,
          }),
      });
      
      function UserProfilePage() {
          const { userId } = Route.useParams();
      
          return (
              <SuspenseLoader>
                  <UserProfile
                      userId={userId}
                      onUpdate={() => console.log('Profile updated')}
                  />
              </SuspenseLoader>
          );
      }
      
      export default UserProfilePage;
      ```
      
      ---
      
      ## Example 4: List with Search and Filtering
      
      ```typescript
      import React, { useState, useMemo } from 'react';
      import { Box, TextField, List, ListItem } from '@mui/material';
      import { useDebounce } from 'use-debounce';
      import { useSuspenseQuery } from '@tanstack/react-query';
      import { userApi } from '../api/userApi';
      
      export const UserList: React.FC = () => {
          const [searchTerm, setSearchTerm] = useState('');
          const [debouncedSearch] = useDebounce(searchTerm, 300);
      
          const { data: users } = useSuspenseQuery({
              queryKey: ['users'],
              queryFn: () => userApi.getUsers(),
          });
      
          // Memoized filtering
          const filteredUsers = useMemo(() => {
              if (!debouncedSearch) return users;
      
              return users.filter(user =>
                  user.name.toLowerCase().includes(debouncedSearch.toLowerCase()) ||
                  user.email.toLowerCase().includes(debouncedSearch.toLowerCase())
              );
          }, [users, debouncedSearch]);
      
          return (
              <Box>
                  <TextField
                      value={searchTerm}
                      onChange={(e) => setSearchTerm(e.target.value)}
                      placeholder='Search users...'
                      fullWidth
                      sx={{ mb: 2 }}
                  />
      
                  <List>
                      {filteredUsers.map(user => (
                          <ListItem key={user.id}>
                              {user.name} - {user.email}
                          </ListItem>
                      ))}
                  </List>
              </Box>
          );
      };
      ```
      
      ---
      
      ## Example 5: Blog with Validation
      
      ```typescript
      import React from 'react';
      import { Box, TextField, Button, Paper } from '@mui/material';
      import { useBlog } from 'react-hook-blog';
      import { zodResolver } from '@hookblog/resolvers/zod';
      import { z } from 'zod';
      import { useMutation, useQueryClient } from '@tanstack/react-query';
      import { userApi } from '../api/userApi';
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      
      const userSchema = z.object({
          username: z.string().min(3).max(50),
          email: z.string().email(),
          firstName: z.string().min(1),
          lastName: z.string().min(1),
      });
      
      type UserBlogData = z.infer<typeof userSchema>;
      
      interface CreateUserBlogProps {
          onSuccess?: () => void;
      }
      
      export const CreateUserBlog: React.FC<CreateUserBlogProps> = ({ onSuccess }) => {
          const queryClient = useQueryClient();
          const { showSuccess, showError } = useMuiSnackbar();
      
          const { register, handleSubmit, blogState: { errors }, reset } = useBlog<UserBlogData>({
              resolver: zodResolver(userSchema),
              defaultValues: {
                  username: '',
                  email: '',
                  firstName: '',
                  lastName: '',
              },
          });
      
          const createMutation = useMutation({
              mutationFn: (data: UserBlogData) => userApi.createUser(data),
      
              onSuccess: () => {
                  queryClient.invalidateQueries({ queryKey: ['users'] });
                  showSuccess('User created successfully');
                  reset();
                  onSuccess?.();
              },
      
              onError: () => {
                  showError('Failed to create user');
              },
          });
      
          const onSubmit = (data: UserBlogData) => {
              createMutation.mutate(data);
          };
      
          return (
              <Paper sx={{ p: 3, maxWidth: 500 }}>
                  <blog onSubmit={handleSubmit(onSubmit)}>
                      <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                          <TextField
                              {...register('username')}
                              label='Username'
                              error={!!errors.username}
                              helperText={errors.username?.message}
                              fullWidth
                          />
      
                          <TextField
                              {...register('email')}
                              label='Email'
                              type='email'
                              error={!!errors.email}
                              helperText={errors.email?.message}
                              fullWidth
                          />
      
                          <TextField
                              {...register('firstName')}
                              label='First Name'
                              error={!!errors.firstName}
                              helperText={errors.firstName?.message}
                              fullWidth
                          />
      
                          <TextField
                              {...register('lastName')}
                              label='Last Name'
                              error={!!errors.lastName}
                              helperText={errors.lastName?.message}
                              fullWidth
                          />
      
                          <Button
                              type='submit'
                              variant='contained'
                              disabled={createMutation.isPending}
                          >
                              {createMutation.isPending ? 'Creating...' : 'Create User'}
                          </Button>
                      </Box>
                  </blog>
              </Paper>
          );
      };
      
      export default CreateUserBlog;
      ```
      
      ---
      
      ## Example 2: Parent Container with Lazy Loading
      
      ```typescript
      import React from 'react';
      import { Box } from '@mui/material';
      import { SuspenseLoader } from '~components/SuspenseLoader';
      
      // Lazy load heavy components
      const UserList = React.lazy(() => import('./UserList'));
      const UserStats = React.lazy(() => import('./UserStats'));
      const ActivityFeed = React.lazy(() => import('./ActivityFeed'));
      
      export const UserDashboard: React.FC = () => {
          return (
              <Box sx={{ p: 2 }}>
                  <SuspenseLoader>
                      <UserStats />
                  </SuspenseLoader>
      
                  <Box sx={{ display: 'flex', gap: 2, mt: 2 }}>
                      <Box sx={{ flex: 2 }}>
                          <SuspenseLoader>
                              <UserList />
                          </SuspenseLoader>
                      </Box>
      
                      <Box sx={{ flex: 1 }}>
                          <SuspenseLoader>
                              <ActivityFeed />
                          </SuspenseLoader>
                      </Box>
                  </Box>
              </Box>
          );
      };
      
      export default UserDashboard;
      ```
      
      **Benefits:**
      - Each section loads independently
      - User sees partial content sooner
      - Better perceived perblogance
      
      ---
      
      ## Example 3: Cache-First Strategy Implementation
      
      Complete example based on useSuspensePost.ts:
      
      ```typescript
      import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query';
      import { postApi } from '../api/postApi';
      import type { Post } from '../types';
      
      /**
       * Smart post hook with cache-first strategy
       * Reuses data from grid cache when available
       */
      export function useSuspensePost(blogId: number, postId: number) {
          const queryClient = useQueryClient();
      
          return useSuspenseQuery<Post, Error>({
              queryKey: ['post', blogId, postId],
              queryFn: async () => {
                  // Strategy 1: Check grid cache first (avoids API call)
                  const gridCache = queryClient.getQueryData<{ rows: Post[] }>([
                      'posts-v2',
                      blogId,
                      'summary'
                  ]) || queryClient.getQueryData<{ rows: Post[] }>([
                      'posts-v2',
                      blogId,
                      'flat'
                  ]);
      
                  if (gridCache?.rows) {
                      const cached = gridCache.rows.find(
                          (row) => row.S_ID === postId
                      );
      
                      if (cached) {
                          return cached;  // Return from cache - no API call!
                      }
                  }
      
                  // Strategy 2: Not in cache, fetch from API
                  return postApi.getPost(blogId, postId);
              },
              staleTime: 5 * 60 * 1000,       // Fresh for 5 minutes
              gcTime: 10 * 60 * 1000,          // Cache for 10 minutes
              refetchOnWindowFocus: false,     // Don't refetch on focus
          });
      }
      ```
      
      **Why this pattern:**
      - Checks grid cache before API
      - Instant data if user came from grid
      - Falls back to API if not cached
      - Configurable cache times
      
      ---
      
      ## Example 4: Complete Route File
      
      ```typescript
      /**
       * Project catalog route
       * Path: /project-catalog
       */
      
      import { createFileRoute } from '@tanstack/react-router';
      import { lazy } from 'react';
      
      // Lazy load the PostTable component
      const PostTable = lazy(() =>
          import('@/features/posts/components/PostTable').then(
              (module) => ({ default: module.PostTable })
          )
      );
      
      // Route constants
      const PROJECT_CATALOG_FORM_ID = 744;
      const PROJECT_CATALOG_PROJECT_ID = 225;
      
      export const Route = createFileRoute('/project-catalog/')({
          component: ProjectCatalogPage,
          loader: () => ({
              crumb: 'Projects',  // Breadcrumb title
          }),
      });
      
      function ProjectCatalogPage() {
          return (
              <PostTable
                  blogId={PROJECT_CATALOG_FORM_ID}
                  projectId={PROJECT_CATALOG_PROJECT_ID}
                  tableType='active_projects'
                  title='Blog Dashboard'
              />
          );
      }
      
      export default ProjectCatalogPage;
      ```
      
      ---
      
      ## Example 5: Dialog with Blog
      
      ```typescript
      import React from 'react';
      import {
          Dialog,
          DialogTitle,
          DialogContent,
          DialogActions,
          Button,
          TextField,
          Box,
          IconButton,
      } from '@mui/material';
      import { Close, PersonAdd } from '@mui/icons-material';
      import { useBlog } from 'react-hook-blog';
      import { zodResolver } from '@hookblog/resolvers/zod';
      import { z } from 'zod';
      
      const blogSchema = z.object({
          name: z.string().min(1),
          email: z.string().email(),
      });
      
      type BlogData = z.infer<typeof blogSchema>;
      
      interface AddUserDialogProps {
          open: boolean;
          onClose: () => void;
          onSubmit: (data: BlogData) => Promise<void>;
      }
      
      export const AddUserDialog: React.FC<AddUserDialogProps> = ({
          open,
          onClose,
          onSubmit,
      }) => {
          const { register, handleSubmit, blogState: { errors }, reset } = useBlog<BlogData>({
              resolver: zodResolver(blogSchema),
          });
      
          const handleClose = () => {
              reset();
              onClose();
          };
      
          const handleBlogSubmit = async (data: BlogData) => {
              await onSubmit(data);
              handleClose();
          };
      
          return (
              <Dialog open={open} onClose={handleClose} maxWidth='sm' fullWidth>
                  <DialogTitle>
                      <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                          <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
                              <PersonAdd color='primary' />
                              Add User
                          </Box>
                          <IconButton onClick={handleClose} size='small'>
                              <Close />
                          </IconButton>
                      </Box>
                  </DialogTitle>
      
                  <blog onSubmit={handleSubmit(handleBlogSubmit)}>
                      <DialogContent>
                          <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                              <TextField
                                  {...register('name')}
                                  label='Name'
                                  error={!!errors.name}
                                  helperText={errors.name?.message}
                                  fullWidth
                                  autoFocus
                              />
      
                              <TextField
                                  {...register('email')}
                                  label='Email'
                                  type='email'
                                  error={!!errors.email}
                                  helperText={errors.email?.message}
                                  fullWidth
                              />
                          </Box>
                      </DialogContent>
      
                      <DialogActions>
                          <Button onClick={handleClose}>Cancel</Button>
                          <Button type='submit' variant='contained'>
                              Add User
                          </Button>
                      </DialogActions>
                  </blog>
              </Dialog>
          );
      };
      ```
      
      ---
      
      ## Example 6: Parallel Data Fetching
      
      ```typescript
      import React from 'react';
      import { Box, Grid, Paper } from '@mui/material';
      import { useSuspenseQueries } from '@tanstack/react-query';
      import { userApi } from '../api/userApi';
      import { statsApi } from '../api/statsApi';
      import { activityApi } from '../api/activityApi';
      
      export const Dashboard: React.FC = () => {
          // Fetch all data in parallel with Suspense
          const [statsQuery, usersQuery, activityQuery] = useSuspenseQueries({
              queries: [
                  {
                      queryKey: ['stats'],
                      queryFn: () => statsApi.getStats(),
                  },
                  {
                      queryKey: ['users', 'active'],
                      queryFn: () => userApi.getActiveUsers(),
                  },
                  {
                      queryKey: ['activity', 'recent'],
                      queryFn: () => activityApi.getRecent(),
                  },
              ],
          });
      
          return (
              <Box sx={{ p: 2 }}>
                  <Grid container spacing={2}>
                      <Grid size={{ xs: 12, md: 4 }}>
                          <Paper sx={{ p: 2 }}>
                              <h3>Stats</h3>
                              <p>Total: {statsQuery.data.total}</p>
                          </Paper>
                      </Grid>
      
                      <Grid size={{ xs: 12, md: 4 }}>
                          <Paper sx={{ p: 2 }}>
                              <h3>Active Users</h3>
                              <p>Count: {usersQuery.data.length}</p>
                          </Paper>
                      </Grid>
      
                      <Grid size={{ xs: 12, md: 4 }}>
                          <Paper sx={{ p: 2 }}>
                              <h3>Recent Activity</h3>
                              <p>Events: {activityQuery.data.length}</p>
                          </Paper>
                      </Grid>
                  </Grid>
              </Box>
          );
      };
      
      // Usage with Suspense
      <SuspenseLoader>
          <Dashboard />
      </SuspenseLoader>
      ```
      
      ---
      
      ## Example 7: Optimistic Update
      
      ```typescript
      import { useMutation, useQueryClient } from '@tanstack/react-query';
      import type { User } from '../types';
      
      export const useToggleUserStatus = () => {
          const queryClient = useQueryClient();
      
          return useMutation({
              mutationFn: (userId: string) => userApi.toggleStatus(userId),
      
              // Optimistic update
              onMutate: async (userId) => {
                  // Cancel outgoing refetches
                  await queryClient.cancelQueries({ queryKey: ['users'] });
      
                  // Snapshot previous value
                  const previousUsers = queryClient.getQueryData<User[]>(['users']);
      
                  // Optimistically update UI
                  queryClient.setQueryData<User[]>(['users'], (old) => {
                      return old?.map(user =>
                          user.id === userId
                              ? { ...user, active: !user.active }
                              : user
                      ) || [];
                  });
      
                  return { previousUsers };
              },
      
              // Rollback on error
              onError: (err, userId, context) => {
                  queryClient.setQueryData(['users'], context?.previousUsers);
              },
      
              // Refetch after mutation
              onSettled: () => {
                  queryClient.invalidateQueries({ queryKey: ['users'] });
              },
          });
      };
      ```
      
      ---
      
      ## Summary
      
      **Key Takeaways:**
      
      1. **Component Pattern**: React.FC + lazy + Suspense + useSuspenseQuery
      2. **Feature Structure**: Organized subdirectories (api/, components/, hooks/, etc.)
      3. **Routing**: Folder-based with lazy loading
      4. **Data Fetching**: useSuspenseQuery with cache-first strategy
      5. **Blogs**: React Hook Blog + Zod validation
      6. **Error Handling**: useMuiSnackbar + onError callbacks
      7. **Perblogance**: useMemo, useCallback, React.memo, debouncing
      8. **Styling**: Inline <100 lines, sx prop, MUI v7 syntax
      
      **See other resources for detailed explanations of each pattern.**
    • component-patterns.md 10.6 KB
      # Component Patterns
      
      Modern React component architecture for the application emphasizing type safety, lazy loading, and Suspense boundaries.
      
      ---
      
      ## React.FC Pattern (PREFERRED)
      
      ### Why React.FC
      
      All components use the `React.FC<Props>` pattern for:
      - Explicit type safety for props
      - Consistent component signatures
      - Clear prop interface documentation
      - Better IDE autocomplete
      
      ### Basic Pattern
      
      ```typescript
      import React from 'react';
      
      interface MyComponentProps {
          /** User ID to display */
          userId: number;
          /** Optional callback when action occurs */
          onAction?: () => void;
      }
      
      export const MyComponent: React.FC<MyComponentProps> = ({ userId, onAction }) => {
          return (
              <div>
                  User: {userId}
              </div>
          );
      };
      
      export default MyComponent;
      ```
      
      **Key Points:**
      - Props interface defined separately with JSDoc comments
      - `React.FC<Props>` provides type safety
      - Destructure props in parameters
      - Default export at bottom
      
      ---
      
      ## Lazy Loading Pattern
      
      ### When to Lazy Load
      
      Lazy load components that are:
      - Heavy (DataGrid, charts, rich text editors)
      - Route-level components
      - Modal/dialog content (not shown initially)
      - Below-the-fold content
      
      ### How to Lazy Load
      
      ```typescript
      import React from 'react';
      
      // Lazy load heavy component
      const PostDataGrid = React.lazy(() =>
          import('./grids/PostDataGrid')
      );
      
      // For named exports
      const MyComponent = React.lazy(() =>
          import('./MyComponent').then(module => ({
              default: module.MyComponent
          }))
      );
      ```
      
      **Example from PostTable.tsx:**
      
      ```typescript
      /**
       * Main post table container component
       */
      import React, { useState, useCallback } from 'react';
      import { Box, Paper } from '@mui/material';
      
      // Lazy load PostDataGrid to optimize bundle size
      const PostDataGrid = React.lazy(() => import('./grids/PostDataGrid'));
      
      import { SuspenseLoader } from '~components/SuspenseLoader';
      
      export const PostTable: React.FC<PostTableProps> = ({ formId }) => {
          return (
              <Box>
                  <SuspenseLoader>
                      <PostDataGrid formId={formId} />
                  </SuspenseLoader>
              </Box>
          );
      };
      
      export default PostTable;
      ```
      
      ---
      
      ## Suspense Boundaries
      
      ### SuspenseLoader Component
      
      **Import:**
      ```typescript
      import { SuspenseLoader } from '~components/SuspenseLoader';
      // Or
      import { SuspenseLoader } from '@/components/SuspenseLoader';
      ```
      
      **Usage:**
      ```typescript
      <SuspenseLoader>
          <LazyLoadedComponent />
      </SuspenseLoader>
      ```
      
      **What it does:**
      - Shows loading indicator while lazy component loads
      - Smooth fade-in animation
      - Consistent loading experience
      - Prevents layout shift
      
      ### Where to Place Suspense Boundaries
      
      **Route Level:**
      ```typescript
      // routes/my-route/index.tsx
      const MyPage = lazy(() => import('@/features/my-feature/components/MyPage'));
      
      function Route() {
          return (
              <SuspenseLoader>
                  <MyPage />
              </SuspenseLoader>
          );
      }
      ```
      
      **Component Level:**
      ```typescript
      function ParentComponent() {
          return (
              <Box>
                  <Header />
                  <SuspenseLoader>
                      <HeavyDataGrid />
                  </SuspenseLoader>
              </Box>
          );
      }
      ```
      
      **Multiple Boundaries:**
      ```typescript
      function Page() {
          return (
              <Box>
                  <SuspenseLoader>
                      <HeaderSection />
                  </SuspenseLoader>
      
                  <SuspenseLoader>
                      <MainContent />
                  </SuspenseLoader>
      
                  <SuspenseLoader>
                      <Sidebar />
                  </SuspenseLoader>
              </Box>
          );
      }
      ```
      
      Each section loads independently, better UX.
      
      ---
      
      ## Component Structure Template
      
      ### Recommended Order
      
      ```typescript
      /**
       * Component description
       * What it does, when to use it
       */
      import React, { useState, useCallback, useMemo, useEffect } from 'react';
      import { Box, Paper, Button } from '@mui/material';
      import type { SxProps, Theme } from '@mui/material';
      import { useSuspenseQuery } from '@tanstack/react-query';
      
      // Feature imports
      import { myFeatureApi } from '../api/myFeatureApi';
      import type { MyData } from '~types/myData';
      
      // Component imports
      import { SuspenseLoader } from '~components/SuspenseLoader';
      
      // Hooks
      import { useAuth } from '@/hooks/useAuth';
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      
      // 1. PROPS INTERFACE (with JSDoc)
      interface MyComponentProps {
          /** The ID of the entity to display */
          entityId: number;
          /** Optional callback when action completes */
          onComplete?: () => void;
          /** Display mode */
          mode?: 'view' | 'edit';
      }
      
      // 2. STYLES (if inline and <100 lines)
      const componentStyles: Record<string, SxProps<Theme>> = {
          container: {
              p: 2,
              display: 'flex',
              flexDirection: 'column',
          },
          header: {
              mb: 2,
              display: 'flex',
              justifyContent: 'space-between',
          },
      };
      
      // 3. COMPONENT DEFINITION
      export const MyComponent: React.FC<MyComponentProps> = ({
          entityId,
          onComplete,
          mode = 'view',
      }) => {
          // 4. HOOKS (in this order)
          // - Context hooks first
          const { user } = useAuth();
          const { showSuccess, showError } = useMuiSnackbar();
      
          // - Data fetching
          const { data } = useSuspenseQuery({
              queryKey: ['myEntity', entityId],
              queryFn: () => myFeatureApi.getEntity(entityId),
          });
      
          // - Local state
          const [selectedItem, setSelectedItem] = useState<string | null>(null);
          const [isEditing, setIsEditing] = useState(mode === 'edit');
      
          // - Memoized values
          const filteredData = useMemo(() => {
              return data.filter(item => item.active);
          }, [data]);
      
          // - Effects
          useEffect(() => {
              // Setup
              return () => {
                  // Cleanup
              };
          }, []);
      
          // 5. EVENT HANDLERS (with useCallback)
          const handleItemSelect = useCallback((itemId: string) => {
              setSelectedItem(itemId);
          }, []);
      
          const handleSave = useCallback(async () => {
              try {
                  await myFeatureApi.updateEntity(entityId, { /* data */ });
                  showSuccess('Entity updated successfully');
                  onComplete?.();
              } catch (error) {
                  showError('Failed to update entity');
              }
          }, [entityId, onComplete, showSuccess, showError]);
      
          // 6. RENDER
          return (
              <Box sx={componentStyles.container}>
                  <Box sx={componentStyles.header}>
                      <h2>My Component</h2>
                      <Button onClick={handleSave}>Save</Button>
                  </Box>
      
                  <Paper sx={{ p: 2 }}>
                      {filteredData.map(item => (
                          <div key={item.id}>{item.name}</div>
                      ))}
                  </Paper>
              </Box>
          );
      };
      
      // 7. EXPORT (default export at bottom)
      export default MyComponent;
      ```
      
      ---
      
      ## Component Separation
      
      ### When to Split Components
      
      **Split into multiple components when:**
      - Component exceeds 300 lines
      - Multiple distinct responsibilities
      - Reusable sections
      - Complex nested JSX
      
      **Example:**
      
      ```typescript
      // ❌ AVOID - Monolithic
      function MassiveComponent() {
          // 500+ lines
          // Search logic
          // Filter logic
          // Grid logic
          // Action panel logic
      }
      
      // ✅ PREFERRED - Modular
      function ParentContainer() {
          return (
              <Box>
                  <SearchAndFilter onFilter={handleFilter} />
                  <DataGrid data={filteredData} />
                  <ActionPanel onAction={handleAction} />
              </Box>
          );
      }
      ```
      
      ### When to Keep Together
      
      **Keep in same file when:**
      - Component < 200 lines
      - Tightly coupled logic
      - Not reusable elsewhere
      - Simple presentation component
      
      ---
      
      ## Export Patterns
      
      ### Named Const + Default Export (PREFERRED)
      
      ```typescript
      export const MyComponent: React.FC<Props> = ({ ... }) => {
          // Component logic
      };
      
      export default MyComponent;
      ```
      
      **Why:**
      - Named export for testing/refactoring
      - Default export for lazy loading convenience
      - Both options available to consumers
      
      ### Lazy Loading Named Exports
      
      ```typescript
      const MyComponent = React.lazy(() =>
          import('./MyComponent').then(module => ({
              default: module.MyComponent
          }))
      );
      ```
      
      ---
      
      ## Component Communication
      
      ### Props Down, Events Up
      
      ```typescript
      // Parent
      function Parent() {
          const [selectedId, setSelectedId] = useState<string | null>(null);
      
          return (
              <Child
                  data={data}                    // Props down
                  onSelect={setSelectedId}       // Events up
              />
          );
      }
      
      // Child
      interface ChildProps {
          data: Data[];
          onSelect: (id: string) => void;
      }
      
      export const Child: React.FC<ChildProps> = ({ data, onSelect }) => {
          return (
              <div onClick={() => onSelect(data[0].id)}>
                  {/* Content */}
              </div>
          );
      };
      ```
      
      ### Avoid Prop Drilling
      
      **Use context for deep nesting:**
      ```typescript
      // ❌ AVOID - Prop drilling 5+ levels
      <A prop={x}>
        <B prop={x}>
          <C prop={x}>
            <D prop={x}>
              <E prop={x} />  // Finally uses it here
            </D>
          </C>
        </B>
      </A>
      
      // ✅ PREFERRED - Context or TanStack Query
      const MyContext = createContext<MyData | null>(null);
      
      function Provider({ children }) {
          const { data } = useSuspenseQuery({ ... });
          return <MyContext.Provider value={data}>{children}</MyContext.Provider>;
      }
      
      function DeepChild() {
          const data = useContext(MyContext);
          // Use data directly
      }
      ```
      
      ---
      
      ## Advanced Patterns
      
      ### Compound Components
      
      ```typescript
      // Card.tsx
      export const Card: React.FC<CardProps> & {
          Header: typeof CardHeader;
          Body: typeof CardBody;
          Footer: typeof CardFooter;
      } = ({ children }) => {
          return <Paper>{children}</Paper>;
      };
      
      Card.Header = CardHeader;
      Card.Body = CardBody;
      Card.Footer = CardFooter;
      
      // Usage
      <Card>
          <Card.Header>Title</Card.Header>
          <Card.Body>Content</Card.Body>
          <Card.Footer>Actions</Card.Footer>
      </Card>
      ```
      
      ### Render Props (Rare, but useful)
      
      ```typescript
      interface DataProviderProps {
          children: (data: Data) => React.ReactNode;
      }
      
      export const DataProvider: React.FC<DataProviderProps> = ({ children }) => {
          const { data } = useSuspenseQuery({ ... });
          return <>{children(data)}</>;
      };
      
      // Usage
      <DataProvider>
          {(data) => <Display data={data} />}
      </DataProvider>
      ```
      
      ---
      
      ## Summary
      
      **Modern Component Recipe:**
      1. `React.FC<Props>` with TypeScript
      2. Lazy load if heavy: `React.lazy(() => import())`
      3. Wrap in `<SuspenseLoader>` for loading
      4. Use `useSuspenseQuery` for data
      5. Import aliases (@/, ~types, ~components)
      6. Event handlers with `useCallback`
      7. Default export at bottom
      8. No early returns for loading states
      
      **See Also:**
      - [data-fetching.md](data-fetching.md) - useSuspenseQuery details
      - [loading-and-error-states.md](loading-and-error-states.md) - Suspense best practices
      - [complete-examples.md](complete-examples.md) - Full working examples
    • data-fetching.md 19.4 KB
      # Data Fetching Patterns
      
      Modern data fetching using TanStack Query with Suspense boundaries, cache-first strategies, and centralized API services.
      
      ---
      
      ## PRIMARY PATTERN: useSuspenseQuery
      
      ### Why useSuspenseQuery?
      
      For **all new components**, use `useSuspenseQuery` instead of regular `useQuery`:
      
      **Benefits:**
      - No `isLoading` checks needed
      - Integrates with Suspense boundaries
      - Cleaner component code
      - Consistent loading UX
      - Better error handling with error boundaries
      
      ### Basic Pattern
      
      ```typescript
      import { useSuspenseQuery } from '@tanstack/react-query';
      import { myFeatureApi } from '../api/myFeatureApi';
      
      export const MyComponent: React.FC<Props> = ({ id }) => {
          // No isLoading - Suspense handles it!
          const { data } = useSuspenseQuery({
              queryKey: ['myEntity', id],
              queryFn: () => myFeatureApi.getEntity(id),
          });
      
          // data is ALWAYS defined here (not undefined | Data)
          return <div>{data.name}</div>;
      };
      
      // Wrap in Suspense boundary
      <SuspenseLoader>
          <MyComponent id={123} />
      </SuspenseLoader>
      ```
      
      ### useSuspenseQuery vs useQuery
      
      | Feature | useSuspenseQuery | useQuery |
      |---------|------------------|----------|
      | Loading state | Handled by Suspense | Manual `isLoading` check |
      | Data type | Always defined | `Data \| undefined` |
      | Use with | Suspense boundaries | Traditional components |
      | Recommended for | **NEW components** | Legacy code only |
      | Error handling | Error boundaries | Manual error state |
      
      **When to use regular useQuery:**
      - Maintaining legacy code
      - Very simple cases without Suspense
      - Polling with background updates
      
      **For new components: Always prefer useSuspenseQuery**
      
      ---
      
      ## Cache-First Strategy
      
      ### Cache-First Pattern Example
      
      **Smart caching** reduces API calls by checking React Query cache first:
      
      ```typescript
      import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query';
      import { postApi } from '../api/postApi';
      
      export function useSuspensePost(postId: number) {
          const queryClient = useQueryClient();
      
          return useSuspenseQuery({
              queryKey: ['post', postId],
              queryFn: async () => {
                  // Strategy 1: Try to get from list cache first
                  const cachedListData = queryClient.getQueryData<{ posts: Post[] }>([
                      'posts',
                      'list'
                  ]);
      
                  if (cachedListData?.posts) {
                      const cachedPost = cachedListData.posts.find(
                          (post) => post.id === postId
                      );
      
                      if (cachedPost) {
                          return cachedPost;  // Return from cache!
                      }
                  }
      
                  // Strategy 2: Not in cache, fetch from API
                  return postApi.getPost(postId);
              },
              staleTime: 5 * 60 * 1000,      // Consider fresh for 5 minutes
              gcTime: 10 * 60 * 1000,         // Keep in cache for 10 minutes
              refetchOnWindowFocus: false,    // Don't refetch on focus
          });
      }
      ```
      
      **Key Points:**
      - Check grid/list cache before API call
      - Avoids redundant requests
      - `staleTime`: How long data is considered fresh
      - `gcTime`: How long unused data stays in cache
      - `refetchOnWindowFocus: false`: User preference
      
      ---
      
      ## Parallel Data Fetching
      
      ### useSuspenseQueries
      
      When fetching multiple independent resources:
      
      ```typescript
      import { useSuspenseQueries } from '@tanstack/react-query';
      
      export const MyComponent: React.FC = () => {
          const [userQuery, settingsQuery, preferencesQuery] = useSuspenseQueries({
              queries: [
                  {
                      queryKey: ['user'],
                      queryFn: () => userApi.getCurrentUser(),
                  },
                  {
                      queryKey: ['settings'],
                      queryFn: () => settingsApi.getSettings(),
                  },
                  {
                      queryKey: ['preferences'],
                      queryFn: () => preferencesApi.getPreferences(),
                  },
              ],
          });
      
          // All data available, Suspense handles loading
          const user = userQuery.data;
          const settings = settingsQuery.data;
          const preferences = preferencesQuery.data;
      
          return <Display user={user} settings={settings} prefs={preferences} />;
      };
      ```
      
      **Benefits:**
      - All queries in parallel
      - Single Suspense boundary
      - Type-safe results
      
      ---
      
      ## Query Keys Organization
      
      ### Naming Convention
      
      ```typescript
      // Entity list
      ['entities', blogId]
      ['entities', blogId, 'summary']    // With view mode
      ['entities', blogId, 'flat']
      
      // Single entity
      ['entity', blogId, entityId]
      
      // Related data
      ['entity', entityId, 'history']
      ['entity', entityId, 'comments']
      
      // User-specific
      ['user', userId, 'profile']
      ['user', userId, 'permissions']
      ```
      
      **Rules:**
      - Start with entity name (plural for lists, singular for one)
      - Include IDs for specificity
      - Add view mode / relationship at end
      - Consistent across app
      
      ### Query Key Examples
      
      ```typescript
      // From useSuspensePost.ts
      queryKey: ['post', blogId, postId]
      queryKey: ['posts-v2', blogId, 'summary']
      
      // Invalidation patterns
      queryClient.invalidateQueries({ queryKey: ['post', blogId] });  // All posts for form
      queryClient.invalidateQueries({ queryKey: ['post'] });          // All posts
      ```
      
      ---
      
      ## API Service Layer Pattern
      
      ### File Structure
      
      Create centralized API service per feature:
      
      ```
      features/
        my-feature/
          api/
            myFeatureApi.ts    # Service layer
      ```
      
      ### Service Pattern (from postApi.ts)
      
      ```typescript
      /**
       * Centralized API service for my-feature operations
       * Uses apiClient for consistent error handling
       */
      import apiClient from '@/lib/apiClient';
      import type { MyEntity, UpdatePayload } from '../types';
      
      export const myFeatureApi = {
          /**
           * Fetch a single entity
           */
          getEntity: async (blogId: number, entityId: number): Promise<MyEntity> => {
              const { data } = await apiClient.get(
                  `/blog/entities/${blogId}/${entityId}`
              );
              return data;
          },
      
          /**
           * Fetch all entities for a form
           */
          getEntities: async (blogId: number, view: 'summary' | 'flat'): Promise<MyEntity[]> => {
              const { data } = await apiClient.get(
                  `/blog/entities/${blogId}`,
                  { params: { view } }
              );
              return data.rows;
          },
      
          /**
           * Update entity
           */
          updateEntity: async (
              blogId: number,
              entityId: number,
              payload: UpdatePayload
          ): Promise<MyEntity> => {
              const { data } = await apiClient.put(
                  `/blog/entities/${blogId}/${entityId}`,
                  payload
              );
              return data;
          },
      
          /**
           * Delete entity
           */
          deleteEntity: async (blogId: number, entityId: number): Promise<void> => {
              await apiClient.delete(`/blog/entities/${blogId}/${entityId}`);
          },
      };
      ```
      
      **Key Points:**
      - Export single object with methods
      - Use `apiClient` (axios instance from `@/lib/apiClient`)
      - Type-safe parameters and returns
      - JSDoc comments for each method
      - Centralized error handling (apiClient handles it)
      
      ---
      
      ## Route Format Rules (IMPORTANT)
      
      ### Correct Format
      
      ```typescript
      // ✅ CORRECT - Direct service path
      await apiClient.get('/blog/posts/123');
      await apiClient.post('/projects/create', data);
      await apiClient.put('/users/update/456', updates);
      await apiClient.get('/email/templates');
      
      // ❌ WRONG - Do NOT add /api/ prefix
      await apiClient.get('/api/blog/posts/123');  // WRONG!
      await apiClient.post('/api/projects/create', data); // WRONG!
      ```
      
      **Microservice Routing:**
      - Form service: `/blog/*`
      - Projects service: `/projects/*`
      - Email service: `/email/*`
      - Users service: `/users/*`
      
      **Why:** API routing is handled by proxy configuration, no `/api/` prefix needed.
      
      ---
      
      ## Mutations
      
      ### Basic Mutation Pattern
      
      ```typescript
      import { useMutation, useQueryClient } from '@tanstack/react-query';
      import { myFeatureApi } from '../api/myFeatureApi';
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      
      export const MyComponent: React.FC = () => {
          const queryClient = useQueryClient();
          const { showSuccess, showError } = useMuiSnackbar();
      
          const updateMutation = useMutation({
              mutationFn: (payload: UpdatePayload) =>
                  myFeatureApi.updateEntity(blogId, entityId, payload),
      
              onSuccess: () => {
                  // Invalidate and refetch
                  queryClient.invalidateQueries({
                      queryKey: ['entity', blogId, entityId]
                  });
                  showSuccess('Entity updated successfully');
              },
      
              onError: (error) => {
                  showError('Failed to update entity');
                  console.error('Update error:', error);
              },
          });
      
          const handleUpdate = () => {
              updateMutation.mutate({ name: 'New Name' });
          };
      
          return (
              <Button
                  onClick={handleUpdate}
                  disabled={updateMutation.isPending}
              >
                  {updateMutation.isPending ? 'Updating...' : 'Update'}
              </Button>
          );
      };
      ```
      
      ### Optimistic Updates
      
      ```typescript
      const updateMutation = useMutation({
          mutationFn: (payload) => myFeatureApi.update(id, payload),
      
          // Optimistic update
          onMutate: async (newData) => {
              // Cancel outgoing refetches
              await queryClient.cancelQueries({ queryKey: ['entity', id] });
      
              // Snapshot current value
              const previousData = queryClient.getQueryData(['entity', id]);
      
              // Optimistically update
              queryClient.setQueryData(['entity', id], (old) => ({
                  ...old,
                  ...newData,
              }));
      
              // Return rollback function
              return { previousData };
          },
      
          // Rollback on error
          onError: (err, newData, context) => {
              queryClient.setQueryData(['entity', id], context.previousData);
              showError('Update failed');
          },
      
          // Refetch after success or error
          onSettled: () => {
              queryClient.invalidateQueries({ queryKey: ['entity', id] });
          },
      });
      ```
      
      ---
      
      ## Advanced Query Patterns
      
      ### Prefetching
      
      ```typescript
      export function usePrefetchEntity() {
          const queryClient = useQueryClient();
      
          return (blogId: number, entityId: number) => {
              return queryClient.prefetchQuery({
                  queryKey: ['entity', blogId, entityId],
                  queryFn: () => myFeatureApi.getEntity(blogId, entityId),
                  staleTime: 5 * 60 * 1000,
              });
          };
      }
      
      // Usage: Prefetch on hover
      <div onMouseEnter={() => prefetch(blogId, id)}>
          <Link to={`/entity/${id}`}>View</Link>
      </div>
      ```
      
      ### Cache Access Without Fetching
      
      ```typescript
      export function useEntityFromCache(blogId: number, entityId: number) {
          const queryClient = useQueryClient();
      
          // Get from cache, don't fetch if missing
          const directCache = queryClient.getQueryData<MyEntity>(['entity', blogId, entityId]);
      
          if (directCache) return directCache;
      
          // Try grid cache
          const gridCache = queryClient.getQueryData<{ rows: MyEntity[] }>(['entities-v2', blogId]);
      
          return gridCache?.rows.find(row => row.id === entityId);
      }
      ```
      
      ### Dependent Queries
      
      ```typescript
      // Fetch user first, then user's settings
      const { data: user } = useSuspenseQuery({
          queryKey: ['user', userId],
          queryFn: () => userApi.getUser(userId),
      });
      
      const { data: settings } = useSuspenseQuery({
          queryKey: ['user', userId, 'settings'],
          queryFn: () => settingsApi.getUserSettings(user.id),
          // Automatically waits for user to load due to Suspense
      });
      ```
      
      ---
      
      ## API Client Configuration
      
      ### Using apiClient
      
      ```typescript
      import apiClient from '@/lib/apiClient';
      
      // apiClient is a configured axios instance
      // Automatically includes:
      // - Base URL configuration
      // - Cookie-based authentication
      // - Error interceptors
      // - Response transformers
      ```
      
      **Do NOT create new axios instances** - use apiClient for consistency.
      
      ---
      
      ## Error Handling in Queries
      
      ### onError Callback
      
      ```typescript
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      
      const { showError } = useMuiSnackbar();
      
      const { data } = useSuspenseQuery({
          queryKey: ['entity', id],
          queryFn: () => myFeatureApi.getEntity(id),
      
          // Handle errors
          onError: (error) => {
              showError('Failed to load entity');
              console.error('Load error:', error);
          },
      });
      ```
      
      ### Error Boundaries
      
      Combine with Error Boundaries for comprehensive error handling:
      
      ```typescript
      import { ErrorBoundary } from 'react-error-boundary';
      
      <ErrorBoundary
          fallback={<ErrorDisplay />}
          onError={(error) => console.error(error)}
      >
          <SuspenseLoader>
              <ComponentWithSuspenseQuery />
          </SuspenseLoader>
      </ErrorBoundary>
      ```
      
      ---
      
      ## Complete Examples
      
      ### Example 1: Simple Entity Fetch
      
      ```typescript
      import React from 'react';
      import { useSuspenseQuery } from '@tanstack/react-query';
      import { Box, Typography } from '@mui/material';
      import { userApi } from '../api/userApi';
      
      interface UserProfileProps {
          userId: string;
      }
      
      export const UserProfile: React.FC<UserProfileProps> = ({ userId }) => {
          const { data: user } = useSuspenseQuery({
              queryKey: ['user', userId],
              queryFn: () => userApi.getUser(userId),
              staleTime: 5 * 60 * 1000,
          });
      
          return (
              <Box>
                  <Typography variant='h5'>{user.name}</Typography>
                  <Typography>{user.email}</Typography>
              </Box>
          );
      };
      
      // Usage with Suspense
      <SuspenseLoader>
          <UserProfile userId='123' />
      </SuspenseLoader>
      ```
      
      ### Example 2: Cache-First Strategy
      
      ```typescript
      import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query';
      import { postApi } from '../api/postApi';
      import type { Post } from '../types';
      
      /**
       * Hook with cache-first strategy
       * Checks grid cache before API call
       */
      export function useSuspensePost(blogId: number, postId: number) {
          const queryClient = useQueryClient();
      
          return useSuspenseQuery<Post, Error>({
              queryKey: ['post', blogId, postId],
              queryFn: async () => {
                  // 1. Check grid cache first
                  const gridCache = queryClient.getQueryData<{ rows: Post[] }>([
                      'posts-v2',
                      blogId,
                      'summary'
                  ]) || queryClient.getQueryData<{ rows: Post[] }>([
                      'posts-v2',
                      blogId,
                      'flat'
                  ]);
      
                  if (gridCache?.rows) {
                      const cached = gridCache.rows.find(row => row.S_ID === postId);
                      if (cached) {
                          return cached;  // Reuse grid data
                      }
                  }
      
                  // 2. Not in cache, fetch directly
                  return postApi.getPost(blogId, postId);
              },
              staleTime: 5 * 60 * 1000,
              gcTime: 10 * 60 * 1000,
              refetchOnWindowFocus: false,
          });
      }
      ```
      
      **Benefits:**
      - Avoids duplicate API calls
      - Instant data if already loaded
      - Falls back to API if not cached
      
      ### Example 3: Parallel Fetching
      
      ```typescript
      import { useSuspenseQueries } from '@tanstack/react-query';
      
      export const Dashboard: React.FC = () => {
          const [statsQuery, projectsQuery, notificationsQuery] = useSuspenseQueries({
              queries: [
                  {
                      queryKey: ['stats'],
                      queryFn: () => statsApi.getStats(),
                  },
                  {
                      queryKey: ['projects', 'active'],
                      queryFn: () => projectsApi.getActiveProjects(),
                  },
                  {
                      queryKey: ['notifications', 'unread'],
                      queryFn: () => notificationsApi.getUnread(),
                  },
              ],
          });
      
          return (
              <Box>
                  <StatsCard data={statsQuery.data} />
                  <ProjectsList projects={projectsQuery.data} />
                  <Notifications items={notificationsQuery.data} />
              </Box>
          );
      };
      ```
      
      ---
      
      ## Mutations with Cache Invalidation
      
      ### Update Mutation
      
      ```typescript
      import { useMutation, useQueryClient } from '@tanstack/react-query';
      import { postApi } from '../api/postApi';
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      
      export const useUpdatePost = () => {
          const queryClient = useQueryClient();
          const { showSuccess, showError } = useMuiSnackbar();
      
          return useMutation({
              mutationFn: ({ blogId, postId, data }: UpdateParams) =>
                  postApi.updatePost(blogId, postId, data),
      
              onSuccess: (data, variables) => {
                  // Invalidate specific post
                  queryClient.invalidateQueries({
                      queryKey: ['post', variables.blogId, variables.postId]
                  });
      
                  // Invalidate list to refresh grid
                  queryClient.invalidateQueries({
                      queryKey: ['posts-v2', variables.blogId]
                  });
      
                  showSuccess('Post updated');
              },
      
              onError: (error) => {
                  showError('Failed to update post');
                  console.error('Update error:', error);
              },
          });
      };
      
      // Usage
      const updatePost = useUpdatePost();
      
      const handleSave = () => {
          updatePost.mutate({
              blogId: 123,
              postId: 456,
              data: { responses: { '101': 'value' } }
          });
      };
      ```
      
      ### Delete Mutation
      
      ```typescript
      export const useDeletePost = () => {
          const queryClient = useQueryClient();
          const { showSuccess, showError } = useMuiSnackbar();
      
          return useMutation({
              mutationFn: ({ blogId, postId }: DeleteParams) =>
                  postApi.deletePost(blogId, postId),
      
              onSuccess: (data, variables) => {
                  // Remove from cache manually (optimistic)
                  queryClient.setQueryData<{ rows: Post[] }>(
                      ['posts-v2', variables.blogId],
                      (old) => ({
                          ...old,
                          rows: old?.rows.filter(row => row.S_ID !== variables.postId) || []
                      })
                  );
      
                  showSuccess('Post deleted');
              },
      
              onError: (error, variables) => {
                  // Rollback - refetch to get accurate state
                  queryClient.invalidateQueries({
                      queryKey: ['posts-v2', variables.blogId]
                  });
                  showError('Failed to delete post');
              },
          });
      };
      ```
      
      ---
      
      ## Query Configuration Best Practices
      
      ### Default Configuration
      
      ```typescript
      // In QueryClientProvider setup
      const queryClient = new QueryClient({
          defaultOptions: {
              queries: {
                  staleTime: 1000 * 60 * 5,        // 5 minutes
                  gcTime: 1000 * 60 * 10,           // 10 minutes (was cacheTime)
                  refetchOnWindowFocus: false,       // Don't refetch on focus
                  refetchOnMount: false,             // Don't refetch on mount if fresh
                  retry: 1,                          // Retry failed queries once
              },
          },
      });
      ```
      
      ### Per-Query Overrides
      
      ```typescript
      // Frequently changing data - shorter staleTime
      useSuspenseQuery({
          queryKey: ['notifications', 'unread'],
          queryFn: () => notificationApi.getUnread(),
          staleTime: 30 * 1000,  // 30 seconds
      });
      
      // Rarely changing data - longer staleTime
      useSuspenseQuery({
          queryKey: ['form', blogId, 'structure'],
          queryFn: () => formApi.getStructure(blogId),
          staleTime: 30 * 60 * 1000,  // 30 minutes
      });
      ```
      
      ---
      
      ## Summary
      
      **Modern Data Fetching Recipe:**
      
      1. **Create API Service**: `features/X/api/XApi.ts` using apiClient
      2. **Use useSuspenseQuery**: In components wrapped by SuspenseLoader
      3. **Cache-First**: Check grid cache before API call
      4. **Query Keys**: Consistent naming ['entity', id]
      5. **Route Format**: `/blog/route` NOT `/api/blog/route`
      6. **Mutations**: invalidateQueries after success
      7. **Error Handling**: onError + useMuiSnackbar
      8. **Type Safety**: Type all parameters and returns
      
      **See Also:**
      - [component-patterns.md](component-patterns.md) - Suspense integration
      - [loading-and-error-states.md](loading-and-error-states.md) - SuspenseLoader usage
      - [complete-examples.md](complete-examples.md) - Full working examples
    • file-organization.md 11.6 KB
      # File Organization
      
      Proper file and directory structure for maintainable, scalable frontend code in the the application.
      
      ---
      
      ## features/ vs components/ Distinction
      
      ### features/ Directory
      
      **Purpose**: Domain-specific features with their own logic, API, and components
      
      **When to use:**
      - Feature has multiple related components
      - Feature has its own API endpoints
      - Feature has domain-specific logic
      - Feature has custom hooks/utilities
      
      **Examples:**
      - `features/posts/` - Project catalog/post management
      - `features/blogs/` - Blog builder and rendering
      - `features/auth/` - Authentication flows
      
      **Structure:**
      ```
      features/
        my-feature/
          api/
            myFeatureApi.ts         # API service layer
          components/
            MyFeatureMain.tsx       # Main component
            SubComponents/          # Related components
          hooks/
            useMyFeature.ts         # Custom hooks
            useSuspenseMyFeature.ts # Suspense hooks
          helpers/
            myFeatureHelpers.ts     # Utility functions
          types/
            index.ts                # TypeScript types
          index.ts                  # Public exports
      ```
      
      ### components/ Directory
      
      **Purpose**: Truly reusable components used across multiple features
      
      **When to use:**
      - Component is used in 3+ places
      - Component is generic (no feature-specific logic)
      - Component is a UI primitive or pattern
      
      **Examples:**
      - `components/SuspenseLoader/` - Loading wrapper
      - `components/CustomAppBar/` - Application header
      - `components/ErrorBoundary/` - Error handling
      - `components/LoadingOverlay/` - Loading overlay
      
      **Structure:**
      ```
      components/
        SuspenseLoader/
          SuspenseLoader.tsx
          SuspenseLoader.test.tsx
        CustomAppBar/
          CustomAppBar.tsx
          CustomAppBar.test.tsx
      ```
      
      ---
      
      ## Feature Directory Structure (Detailed)
      
      ### Complete Feature Example
      
      Based on `features/posts/` structure:
      
      ```
      features/
        posts/
          api/
            postApi.ts              # API service layer (GET, POST, PUT, DELETE)
      
          components/
            PostTable.tsx           # Main container component
            grids/
              PostDataGrid/
                PostDataGrid.tsx
            drawers/
              ProjectPostDrawer/
                ProjectPostDrawer.tsx
            cells/
              editors/
                TextEditCell.tsx
              renderers/
                DateCell.tsx
            toolbar/
              CustomToolbar.tsx
      
          hooks/
            usePostQueries.ts       # Regular queries
            useSuspensePost.ts      # Suspense queries
            usePostMutations.ts     # Mutations
            useGridLayout.ts              # Feature-specific hooks
      
          helpers/
            postHelpers.ts          # Utility functions
            validation.ts                 # Validation logic
      
          types/
            index.ts                      # TypeScript types/interfaces
      
          queries/
            postQueries.ts          # Query key factories (optional)
      
          context/
            PostContext.tsx         # React context (if needed)
      
          index.ts                        # Public API exports
      ```
      
      ### Subdirectory Guidelines
      
      #### api/ Directory
      
      **Purpose**: Centralized API calls for the feature
      
      **Files:**
      - `{feature}Api.ts` - Main API service
      
      **Pattern:**
      ```typescript
      // features/my-feature/api/myFeatureApi.ts
      import apiClient from '@/lib/apiClient';
      
      export const myFeatureApi = {
          getItem: async (id: number) => {
              const { data } = await apiClient.get(`/blog/items/${id}`);
              return data;
          },
          createItem: async (payload) => {
              const { data } = await apiClient.post('/blog/items', payload);
              return data;
          },
      };
      ```
      
      #### components/ Directory
      
      **Purpose**: Feature-specific components
      
      **Organization:**
      - Flat structure if <5 components
      - Subdirectories by responsibility if >5 components
      
      **Examples:**
      ```
      components/
        MyFeatureMain.tsx           # Main component
        MyFeatureHeader.tsx         # Supporting components
        MyFeatureFooter.tsx
      
        # OR with subdirectories:
        containers/
          MyFeatureContainer.tsx
        presentational/
          MyFeatureDisplay.tsx
        blogs/
          MyFeatureBlog.tsx
      ```
      
      #### hooks/ Directory
      
      **Purpose**: Custom hooks for the feature
      
      **Naming:**
      - `use` prefix (camelCase)
      - Descriptive of what they do
      
      **Examples:**
      ```
      hooks/
        useMyFeature.ts               # Main hook
        useSuspenseMyFeature.ts       # Suspense version
        useMyFeatureMutations.ts      # Mutations
        useMyFeatureFilters.ts        # Filters/search
      ```
      
      #### helpers/ Directory
      
      **Purpose**: Utility functions specific to the feature
      
      **Examples:**
      ```
      helpers/
        myFeatureHelpers.ts           # General utilities
        validation.ts                 # Validation logic
        transblogers.ts               # Data transblogations
        constants.ts                  # Constants
      ```
      
      #### types/ Directory
      
      **Purpose**: TypeScript types and interfaces
      
      **Files:**
      ```
      types/
        index.ts                      # Main types, exported
        internal.ts                   # Internal types (not exported)
      ```
      
      ---
      
      ## Import Aliases (Vite Configuration)
      
      ### Available Aliases
      
      From `vite.config.ts` lines 180-185:
      
      | Alias | Resolves To | Use For |
      |-------|-------------|---------|
      | `@/` | `src/` | Absolute imports from src root |
      | `~types` | `src/types` | Shared TypeScript types |
      | `~components` | `src/components` | Reusable components |
      | `~features` | `src/features` | Feature imports |
      
      ### Usage Examples
      
      ```typescript
      // ✅ PREFERRED - Use aliases for absolute imports
      import { apiClient } from '@/lib/apiClient';
      import { SuspenseLoader } from '~components/SuspenseLoader';
      import { postApi } from '~features/posts/api/postApi';
      import type { User } from '~types/user';
      
      // ❌ AVOID - Relative paths from deep nesting
      import { apiClient } from '../../../lib/apiClient';
      import { SuspenseLoader } from '../../../components/SuspenseLoader';
      ```
      
      ### When to Use Which Alias
      
      **@/ (General)**:
      - Lib utilities: `@/lib/apiClient`
      - Hooks: `@/hooks/useAuth`
      - Config: `@/config/theme`
      - Shared services: `@/services/authService`
      
      **~types (Type Imports)**:
      ```typescript
      import type { Post } from '~types/post';
      import type { User, UserRole } from '~types/user';
      ```
      
      **~components (Reusable Components)**:
      ```typescript
      import { SuspenseLoader } from '~components/SuspenseLoader';
      import { CustomAppBar } from '~components/CustomAppBar';
      import { ErrorBoundary } from '~components/ErrorBoundary';
      ```
      
      **~features (Feature Imports)**:
      ```typescript
      import { postApi } from '~features/posts/api/postApi';
      import { useAuth } from '~features/auth/hooks/useAuth';
      ```
      
      ---
      
      ## File Naming Conventions
      
      ### Components
      
      **Pattern**: PascalCase with `.tsx` extension
      
      ```
      MyComponent.tsx
      PostDataGrid.tsx
      CustomAppBar.tsx
      ```
      
      **Avoid:**
      - camelCase: `myComponent.tsx` ❌
      - kebab-case: `my-component.tsx` ❌
      - All caps: `MYCOMPONENT.tsx` ❌
      
      ### Hooks
      
      **Pattern**: camelCase with `use` prefix, `.ts` extension
      
      ```
      useMyFeature.ts
      useSuspensePost.ts
      useAuth.ts
      useGridLayout.ts
      ```
      
      ### API Services
      
      **Pattern**: camelCase with `Api` suffix, `.ts` extension
      
      ```
      myFeatureApi.ts
      postApi.ts
      userApi.ts
      ```
      
      ### Helpers/Utilities
      
      **Pattern**: camelCase with descriptive name, `.ts` extension
      
      ```
      myFeatureHelpers.ts
      validation.ts
      transblogers.ts
      constants.ts
      ```
      
      ### Types
      
      **Pattern**: camelCase, `index.ts` or descriptive name
      
      ```
      types/index.ts
      types/post.ts
      types/user.ts
      ```
      
      ---
      
      ## When to Create a New Feature
      
      ### Create New Feature When:
      
      - Multiple related components (>3)
      - Has own API endpoints
      - Domain-specific logic
      - Will grow over time
      - Reused across multiple routes
      
      **Example:** `features/posts/`
      - 20+ components
      - Own API service
      - Complex state management
      - Used in multiple routes
      
      ### Add to Existing Feature When:
      
      - Related to existing feature
      - Shares same API
      - Logically grouped
      - Extends existing functionality
      
      **Example:** Adding export dialog to posts feature
      
      ### Create Reusable Component When:
      
      - Used across 3+ features
      - Generic, no domain logic
      - Pure presentation
      - Shared pattern
      
      **Example:** `components/SuspenseLoader/`
      
      ---
      
      ## Import Organization
      
      ### Import Order (Recommended)
      
      ```typescript
      // 1. React and React-related
      import React, { useState, useCallback, useMemo } from 'react';
      import { lazy } from 'react';
      
      // 2. Third-party libraries (alphabetical)
      import { Box, Paper, Button, Grid } from '@mui/material';
      import type { SxProps, Theme } from '@mui/material';
      import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query';
      import { createFileRoute } from '@tanstack/react-router';
      
      // 3. Alias imports (@ first, then ~)
      import { apiClient } from '@/lib/apiClient';
      import { useAuth } from '@/hooks/useAuth';
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      import { SuspenseLoader } from '~components/SuspenseLoader';
      import { postApi } from '~features/posts/api/postApi';
      
      // 4. Type imports (grouped)
      import type { Post } from '~types/post';
      import type { User } from '~types/user';
      
      // 5. Relative imports (same feature)
      import { MySubComponent } from './MySubComponent';
      import { useMyFeature } from '../hooks/useMyFeature';
      import { myFeatureHelpers } from '../helpers/myFeatureHelpers';
      ```
      
      **Use single quotes** for all imports (project standard)
      
      ---
      
      ## Public API Pattern
      
      ### feature/index.ts
      
      Export public API from feature for clean imports:
      
      ```typescript
      // features/my-feature/index.ts
      
      // Export main components
      export { MyFeatureMain } from './components/MyFeatureMain';
      export { MyFeatureHeader } from './components/MyFeatureHeader';
      
      // Export hooks
      export { useMyFeature } from './hooks/useMyFeature';
      export { useSuspenseMyFeature } from './hooks/useSuspenseMyFeature';
      
      // Export API
      export { myFeatureApi } from './api/myFeatureApi';
      
      // Export types
      export type { MyFeatureData, MyFeatureConfig } from './types';
      ```
      
      **Usage:**
      ```typescript
      // ✅ Clean import from feature index
      import { MyFeatureMain, useMyFeature } from '~features/my-feature';
      
      // ❌ Avoid deep imports (but OK if needed)
      import { MyFeatureMain } from '~features/my-feature/components/MyFeatureMain';
      ```
      
      ---
      
      ## Directory Structure Visualization
      
      ```
      src/
      ├── features/                    # Domain-specific features
      │   ├── posts/
      │   │   ├── api/
      │   │   ├── components/
      │   │   ├── hooks/
      │   │   ├── helpers/
      │   │   ├── types/
      │   │   └── index.ts
      │   ├── blogs/
      │   └── auth/
      │
      ├── components/                  # Reusable components
      │   ├── SuspenseLoader/
      │   ├── CustomAppBar/
      │   ├── ErrorBoundary/
      │   └── LoadingOverlay/
      │
      ├── routes/                      # TanStack Router routes
      │   ├── __root.tsx
      │   ├── index.tsx
      │   ├── project-catalog/
      │   │   ├── index.tsx
      │   │   └── create/
      │   └── blogs/
      │
      ├── hooks/                       # Shared hooks
      │   ├── useAuth.ts
      │   ├── useMuiSnackbar.ts
      │   └── useDebounce.ts
      │
      ├── lib/                         # Shared utilities
      │   ├── apiClient.ts
      │   └── utils.ts
      │
      ├── types/                       # Shared TypeScript types
      │   ├── user.ts
      │   ├── post.ts
      │   └── common.ts
      │
      ├── config/                      # Configuration
      │   └── theme.ts
      │
      └── App.tsx                      # Root component
      ```
      
      ---
      
      ## Summary
      
      **Key Principles:**
      1. **features/** for domain-specific code
      2. **components/** for truly reusable UI
      3. Use subdirectories: api/, components/, hooks/, helpers/, types/
      4. Import aliases for clean imports (@/, ~types, ~components, ~features)
      5. Consistent naming: PascalCase components, camelCase utilities
      6. Export public API from feature index.ts
      
      **See Also:**
      - [component-patterns.md](component-patterns.md) - Component structure
      - [data-fetching.md](data-fetching.md) - API service patterns
      - [complete-examples.md](complete-examples.md) - Full feature example
    • loading-and-error-states.md 11.8 KB
      # Loading & Error States
      
      **CRITICAL**: Proper loading and error state handling prevents layout shift and provides better user experience.
      
      ---
      
      ## ⚠️ CRITICAL RULE: Never Use Early Returns
      
      ### The Problem
      
      ```typescript
      // ❌ NEVER DO THIS - Early return with loading spinner
      const Component = () => {
          const { data, isLoading } = useQuery();
      
          // WRONG: This causes layout shift and poor UX
          if (isLoading) {
              return <LoadingSpinner />;
          }
      
          return <Content data={data} />;
      };
      ```
      
      **Why this is bad:**
      1. **Layout Shift**: Content position jumps when loading completes
      2. **CLS (Cumulative Layout Shift)**: Poor Core Web Vital score
      3. **Jarring UX**: Page structure changes suddenly
      4. **Lost Scroll Position**: User loses place on page
      
      ### The Solutions
      
      **Option 1: SuspenseLoader (PREFERRED for new components)**
      
      ```typescript
      import { SuspenseLoader } from '~components/SuspenseLoader';
      
      const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
      
      export const MyComponent: React.FC = () => {
          return (
              <SuspenseLoader>
                  <HeavyComponent />
              </SuspenseLoader>
          );
      };
      ```
      
      **Option 2: LoadingOverlay (for legacy useQuery patterns)**
      
      ```typescript
      import { LoadingOverlay } from '~components/LoadingOverlay';
      
      export const MyComponent: React.FC = () => {
          const { data, isLoading } = useQuery({ ... });
      
          return (
              <LoadingOverlay loading={isLoading}>
                  <Content data={data} />
              </LoadingOverlay>
          );
      };
      ```
      
      ---
      
      ## SuspenseLoader Component
      
      ### What It Does
      
      - Shows loading indicator while lazy components load
      - Smooth fade-in animation
      - Prevents layout shift
      - Consistent loading experience across app
      
      ### Import
      
      ```typescript
      import { SuspenseLoader } from '~components/SuspenseLoader';
      // Or
      import { SuspenseLoader } from '@/components/SuspenseLoader';
      ```
      
      ### Basic Usage
      
      ```typescript
      <SuspenseLoader>
          <LazyLoadedComponent />
      </SuspenseLoader>
      ```
      
      ### With useSuspenseQuery
      
      ```typescript
      import { useSuspenseQuery } from '@tanstack/react-query';
      import { SuspenseLoader } from '~components/SuspenseLoader';
      
      const Inner: React.FC = () => {
          // No isLoading needed!
          const { data } = useSuspenseQuery({
              queryKey: ['data'],
              queryFn: () => api.getData(),
          });
      
          return <Display data={data} />;
      };
      
      // Outer component wraps in Suspense
      export const Outer: React.FC = () => {
          return (
              <SuspenseLoader>
                  <Inner />
              </SuspenseLoader>
          );
      };
      ```
      
      ### Multiple Suspense Boundaries
      
      **Pattern**: Separate loading for independent sections
      
      ```typescript
      export const Dashboard: React.FC = () => {
          return (
              <Box>
                  <SuspenseLoader>
                      <Header />
                  </SuspenseLoader>
      
                  <SuspenseLoader>
                      <MainContent />
                  </SuspenseLoader>
      
                  <SuspenseLoader>
                      <Sidebar />
                  </SuspenseLoader>
              </Box>
          );
      };
      ```
      
      **Benefits:**
      - Each section loads independently
      - User sees partial content sooner
      - Better perceived performance
      
      ### Nested Suspense
      
      ```typescript
      export const ParentComponent: React.FC = () => {
          return (
              <SuspenseLoader>
                  {/* Parent suspends while loading */}
                  <ParentContent>
                      <SuspenseLoader>
                          {/* Nested suspense for child */}
                          <ChildComponent />
                      </SuspenseLoader>
                  </ParentContent>
              </SuspenseLoader>
          );
      };
      ```
      
      ---
      
      ## LoadingOverlay Component
      
      ### When to Use
      
      - Legacy components with `useQuery` (not refactored to Suspense yet)
      - Overlay loading state needed
      - Can't use Suspense boundaries
      
      ### Usage
      
      ```typescript
      import { LoadingOverlay } from '~components/LoadingOverlay';
      
      export const MyComponent: React.FC = () => {
          const { data, isLoading } = useQuery({
              queryKey: ['data'],
              queryFn: () => api.getData(),
          });
      
          return (
              <LoadingOverlay loading={isLoading}>
                  <Box sx={{ p: 2 }}>
                      {data && <Content data={data} />}
                  </Box>
              </LoadingOverlay>
          );
      };
      ```
      
      **What it does:**
      - Shows semi-transparent overlay with spinner
      - Content area reserved (no layout shift)
      - Prevents interaction while loading
      
      ---
      
      ## Error Handling
      
      ### useMuiSnackbar Hook (REQUIRED)
      
      **NEVER use react-toastify** - Project standard is MUI Snackbar
      
      ```typescript
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      
      export const MyComponent: React.FC = () => {
          const { showSuccess, showError, showInfo, showWarning } = useMuiSnackbar();
      
          const handleAction = async () => {
              try {
                  await api.doSomething();
                  showSuccess('Operation completed successfully');
              } catch (error) {
                  showError('Operation failed');
              }
          };
      
          return <Button onClick={handleAction}>Do Action</Button>;
      };
      ```
      
      **Available Methods:**
      - `showSuccess(message)` - Green success message
      - `showError(message)` - Red error message
      - `showWarning(message)` - Orange warning message
      - `showInfo(message)` - Blue info message
      
      ### TanStack Query Error Callbacks
      
      ```typescript
      import { useSuspenseQuery } from '@tanstack/react-query';
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      
      export const MyComponent: React.FC = () => {
          const { showError } = useMuiSnackbar();
      
          const { data } = useSuspenseQuery({
              queryKey: ['data'],
              queryFn: () => api.getData(),
      
              // Handle errors
              onError: (error) => {
                  showError('Failed to load data');
                  console.error('Query error:', error);
              },
          });
      
          return <Content data={data} />;
      };
      ```
      
      ### Error Boundaries
      
      ```typescript
      import { ErrorBoundary } from 'react-error-boundary';
      
      function ErrorFallback({ error, resetErrorBoundary }) {
          return (
              <Box sx={{ p: 4, textAlign: 'center' }}>
                  <Typography variant='h5' color='error'>
                      Something went wrong
                  </Typography>
                  <Typography>{error.message}</Typography>
                  <Button onClick={resetErrorBoundary}>Try Again</Button>
              </Box>
          );
      }
      
      export const MyPage: React.FC = () => {
          return (
              <ErrorBoundary
                  FallbackComponent={ErrorFallback}
                  onError={(error) => console.error('Boundary caught:', error)}
              >
                  <SuspenseLoader>
                      <ComponentThatMightError />
                  </SuspenseLoader>
              </ErrorBoundary>
          );
      };
      ```
      
      ---
      
      ## Complete Examples
      
      ### Example 1: Modern Component with Suspense
      
      ```typescript
      import React from 'react';
      import { Box, Paper } from '@mui/material';
      import { useSuspenseQuery } from '@tanstack/react-query';
      import { SuspenseLoader } from '~components/SuspenseLoader';
      import { myFeatureApi } from '../api/myFeatureApi';
      
      // Inner component uses useSuspenseQuery
      const InnerComponent: React.FC<{ id: number }> = ({ id }) => {
          const { data } = useSuspenseQuery({
              queryKey: ['entity', id],
              queryFn: () => myFeatureApi.getEntity(id),
          });
      
          // data is always defined - no isLoading needed!
          return (
              <Paper sx={{ p: 2 }}>
                  <h2>{data.title}</h2>
                  <p>{data.description}</p>
              </Paper>
          );
      };
      
      // Outer component provides Suspense boundary
      export const OuterComponent: React.FC<{ id: number }> = ({ id }) => {
          return (
              <Box>
                  <SuspenseLoader>
                      <InnerComponent id={id} />
                  </SuspenseLoader>
              </Box>
          );
      };
      
      export default OuterComponent;
      ```
      
      ### Example 2: Legacy Pattern with LoadingOverlay
      
      ```typescript
      import React from 'react';
      import { Box } from '@mui/material';
      import { useQuery } from '@tanstack/react-query';
      import { LoadingOverlay } from '~components/LoadingOverlay';
      import { myFeatureApi } from '../api/myFeatureApi';
      
      export const LegacyComponent: React.FC<{ id: number }> = ({ id }) => {
          const { data, isLoading, error } = useQuery({
              queryKey: ['entity', id],
              queryFn: () => myFeatureApi.getEntity(id),
          });
      
          return (
              <LoadingOverlay loading={isLoading}>
                  <Box sx={{ p: 2 }}>
                      {error && <ErrorDisplay error={error} />}
                      {data && <Content data={data} />}
                  </Box>
              </LoadingOverlay>
          );
      };
      ```
      
      ### Example 3: Error Handling with Snackbar
      
      ```typescript
      import React from 'react';
      import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query';
      import { Button } from '@mui/material';
      import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
      import { myFeatureApi } from '../api/myFeatureApi';
      
      export const EntityEditor: React.FC<{ id: number }> = ({ id }) => {
          const queryClient = useQueryClient();
          const { showSuccess, showError } = useMuiSnackbar();
      
          const { data } = useSuspenseQuery({
              queryKey: ['entity', id],
              queryFn: () => myFeatureApi.getEntity(id),
              onError: () => {
                  showError('Failed to load entity');
              },
          });
      
          const updateMutation = useMutation({
              mutationFn: (updates) => myFeatureApi.update(id, updates),
      
              onSuccess: () => {
                  queryClient.invalidateQueries({ queryKey: ['entity', id] });
                  showSuccess('Entity updated successfully');
              },
      
              onError: () => {
                  showError('Failed to update entity');
              },
          });
      
          return (
              <Button onClick={() => updateMutation.mutate({ name: 'New' })}>
                  Update
              </Button>
          );
      };
      ```
      
      ---
      
      ## Loading State Anti-Patterns
      
      ### ❌ What NOT to Do
      
      ```typescript
      // ❌ NEVER - Early return
      if (isLoading) {
          return <CircularProgress />;
      }
      
      // ❌ NEVER - Conditional rendering
      {isLoading ? <Spinner /> : <Content />}
      
      // ❌ NEVER - Layout changes
      if (isLoading) {
          return (
              <Box sx={{ height: 100 }}>
                  <Spinner />
              </Box>
          );
      }
      return (
          <Box sx={{ height: 500 }}>  // Different height!
              <Content />
          </Box>
      );
      ```
      
      ### ✅ What TO Do
      
      ```typescript
      // ✅ BEST - useSuspenseQuery + SuspenseLoader
      <SuspenseLoader>
          <ComponentWithSuspenseQuery />
      </SuspenseLoader>
      
      // ✅ ACCEPTABLE - LoadingOverlay
      <LoadingOverlay loading={isLoading}>
          <Content />
      </LoadingOverlay>
      
      // ✅ OK - Inline skeleton with same layout
      <Box sx={{ height: 500 }}>
          {isLoading ? <Skeleton variant='rectangular' height='100%' /> : <Content />}
      </Box>
      ```
      
      ---
      
      ## Skeleton Loading (Alternative)
      
      ### MUI Skeleton Component
      
      ```typescript
      import { Skeleton, Box } from '@mui/material';
      
      export const MyComponent: React.FC = () => {
          const { data, isLoading } = useQuery({ ... });
      
          return (
              <Box sx={{ p: 2 }}>
                  {isLoading ? (
                      <>
                          <Skeleton variant='text' width={200} height={40} />
                          <Skeleton variant='rectangular' width='100%' height={200} />
                          <Skeleton variant='text' width='100%' />
                      </>
                  ) : (
                      <>
                          <Typography variant='h5'>{data.title}</Typography>
                          <img src={data.image} />
                          <Typography>{data.description}</Typography>
                      </>
                  )}
              </Box>
          );
      };
      ```
      
      **Key**: Skeleton must have **same layout** as actual content (no shift)
      
      ---
      
      ## Summary
      
      **Loading States:**
      - ✅ **PREFERRED**: SuspenseLoader + useSuspenseQuery (modern pattern)
      - ✅ **ACCEPTABLE**: LoadingOverlay (legacy pattern)
      - ✅ **OK**: Skeleton with same layout
      - ❌ **NEVER**: Early returns or conditional layout
      
      **Error Handling:**
      - ✅ **ALWAYS**: useMuiSnackbar for user feedback
      - ❌ **NEVER**: react-toastify
      - ✅ Use onError callbacks in queries/mutations
      - ✅ Error boundaries for component-level errors
      
      **See Also:**
      - [component-patterns.md](component-patterns.md) - Suspense integration
      - [data-fetching.md](data-fetching.md) - useSuspenseQuery details
    • performance.md 9.4 KB
      # Performance Optimization
      
      Patterns for optimizing React component performance, preventing unnecessary re-renders, and avoiding memory leaks.
      
      ---
      
      ## Memoization Patterns
      
      ### useMemo for Expensive Computations
      
      ```typescript
      import { useMemo } from 'react';
      
      export const DataDisplay: React.FC<{ items: Item[], searchTerm: string }> = ({
          items,
          searchTerm,
      }) => {
          // ❌ AVOID - Runs on every render
          const filteredItems = items
              .filter(item => item.name.includes(searchTerm))
              .sort((a, b) => a.name.localeCompare(b.name));
      
          // ✅ CORRECT - Memoized, only recalculates when dependencies change
          const filteredItems = useMemo(() => {
              return items
                  .filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase()))
                  .sort((a, b) => a.name.localeCompare(b.name));
          }, [items, searchTerm]);
      
          return <List items={filteredItems} />;
      };
      ```
      
      **When to use useMemo:**
      - Filtering/sorting large arrays
      - Complex calculations
      - Transforming data structures
      - Expensive computations (loops, recursion)
      
      **When NOT to use useMemo:**
      - Simple string concatenation
      - Basic arithmetic
      - Premature optimization (profile first!)
      
      ---
      
      ## useCallback for Event Handlers
      
      ### The Problem
      
      ```typescript
      // ❌ AVOID - Creates new function on every render
      export const Parent: React.FC = () => {
          const handleClick = (id: string) => {
              console.log('Clicked:', id);
          };
      
          // Child re-renders every time Parent renders
          // because handleClick is a new function reference each time
          return <Child onClick={handleClick} />;
      };
      ```
      
      ### The Solution
      
      ```typescript
      import { useCallback } from 'react';
      
      export const Parent: React.FC = () => {
          // ✅ CORRECT - Stable function reference
          const handleClick = useCallback((id: string) => {
              console.log('Clicked:', id);
          }, []); // Empty deps = function never changes
      
          // Child only re-renders when props actually change
          return <Child onClick={handleClick} />;
      };
      ```
      
      **When to use useCallback:**
      - Functions passed as props to children
      - Functions used as dependencies in useEffect
      - Functions passed to memoized components
      - Event handlers in lists
      
      **When NOT to use useCallback:**
      - Event handlers not passed to children
      - Simple inline handlers: `onClick={() => doSomething()}`
      
      ---
      
      ## React.memo for Component Memoization
      
      ### Basic Usage
      
      ```typescript
      import React from 'react';
      
      interface ExpensiveComponentProps {
          data: ComplexData;
          onAction: () => void;
      }
      
      // ✅ Wrap expensive components in React.memo
      export const ExpensiveComponent = React.memo<ExpensiveComponentProps>(
          function ExpensiveComponent({ data, onAction }) {
              // Complex rendering logic
              return <ComplexVisualization data={data} />;
          }
      );
      ```
      
      **When to use React.memo:**
      - Component renders frequently
      - Component has expensive rendering
      - Props don't change often
      - Component is a list item
      - DataGrid cells/renderers
      
      **When NOT to use React.memo:**
      - Props change frequently anyway
      - Rendering is already fast
      - Premature optimization
      
      ---
      
      ## Debounced Search
      
      ### Using use-debounce Hook
      
      ```typescript
      import { useState } from 'react';
      import { useDebounce } from 'use-debounce';
      import { useSuspenseQuery } from '@tanstack/react-query';
      
      export const SearchComponent: React.FC = () => {
          const [searchTerm, setSearchTerm] = useState('');
      
          // Debounce for 300ms
          const [debouncedSearchTerm] = useDebounce(searchTerm, 300);
      
          // Query uses debounced value
          const { data } = useSuspenseQuery({
              queryKey: ['search', debouncedSearchTerm],
              queryFn: () => api.search(debouncedSearchTerm),
              enabled: debouncedSearchTerm.length > 0,
          });
      
          return (
              <input
                  value={searchTerm}
                  onChange={(e) => setSearchTerm(e.target.value)}
                  placeholder='Search...'
              />
          );
      };
      ```
      
      **Optimal Debounce Timing:**
      - **300-500ms**: Search/filtering
      - **1000ms**: Auto-save
      - **100-200ms**: Real-time validation
      
      ---
      
      ## Memory Leak Prevention
      
      ### Cleanup Timeouts/Intervals
      
      ```typescript
      import { useEffect, useState } from 'react';
      
      export const MyComponent: React.FC = () => {
          const [count, setCount] = useState(0);
      
          useEffect(() => {
              // ✅ CORRECT - Cleanup interval
              const intervalId = setInterval(() => {
                  setCount(c => c + 1);
              }, 1000);
      
              return () => {
                  clearInterval(intervalId);  // Cleanup!
              };
          }, []);
      
          useEffect(() => {
              // ✅ CORRECT - Cleanup timeout
              const timeoutId = setTimeout(() => {
                  console.log('Delayed action');
              }, 5000);
      
              return () => {
                  clearTimeout(timeoutId);  // Cleanup!
              };
          }, []);
      
          return <div>{count}</div>;
      };
      ```
      
      ### Cleanup Event Listeners
      
      ```typescript
      useEffect(() => {
          const handleResize = () => {
              console.log('Resized');
          };
      
          window.addEventListener('resize', handleResize);
      
          return () => {
              window.removeEventListener('resize', handleResize);  // Cleanup!
          };
      }, []);
      ```
      
      ### Abort Controllers for Fetch
      
      ```typescript
      useEffect(() => {
          const abortController = new AbortController();
      
          fetch('/api/data', { signal: abortController.signal })
              .then(response => response.json())
              .then(data => setState(data))
              .catch(error => {
                  if (error.name === 'AbortError') {
                      console.log('Fetch aborted');
                  }
              });
      
          return () => {
              abortController.abort();  // Cleanup!
          };
      }, []);
      ```
      
      **Note**: With TanStack Query, this is handled automatically.
      
      ---
      
      ## Form Performance
      
      ### Watch Specific Fields (Not All)
      
      ```typescript
      import { useForm } from 'react-hook-form';
      
      export const MyForm: React.FC = () => {
          const { register, watch, handleSubmit } = useForm();
      
          // ❌ AVOID - Watches all fields, re-renders on any change
          const formValues = watch();
      
          // ✅ CORRECT - Watch only what you need
          const username = watch('username');
          const email = watch('email');
      
          // Or multiple specific fields
          const [username, email] = watch(['username', 'email']);
      
          return (
              <form onSubmit={handleSubmit(onSubmit)}>
                  <input {...register('username')} />
                  <input {...register('email')} />
                  <input {...register('password')} />
      
                  {/* Only re-renders when username/email change */}
                  <p>Username: {username}, Email: {email}</p>
              </form>
          );
      };
      ```
      
      ---
      
      ## List Rendering Optimization
      
      ### Key Prop Usage
      
      ```typescript
      // ✅ CORRECT - Stable unique keys
      {items.map(item => (
          <ListItem key={item.id}>
              {item.name}
          </ListItem>
      ))}
      
      // ❌ AVOID - Index as key (unstable if list changes)
      {items.map((item, index) => (
          <ListItem key={index}>  // WRONG if list reorders
              {item.name}
          </ListItem>
      ))}
      ```
      
      ### Memoized List Items
      
      ```typescript
      const ListItem = React.memo<ListItemProps>(({ item, onAction }) => {
          return (
              <Box onClick={() => onAction(item.id)}>
                  {item.name}
              </Box>
          );
      });
      
      export const List: React.FC<{ items: Item[] }> = ({ items }) => {
          const handleAction = useCallback((id: string) => {
              console.log('Action:', id);
          }, []);
      
          return (
              <Box>
                  {items.map(item => (
                      <ListItem
                          key={item.id}
                          item={item}
                          onAction={handleAction}
                      />
                  ))}
              </Box>
          );
      };
      ```
      
      ---
      
      ## Preventing Component Re-initialization
      
      ### The Problem
      
      ```typescript
      // ❌ AVOID - Component recreated on every render
      export const Parent: React.FC = () => {
          // New component definition each render!
          const ChildComponent = () => <div>Child</div>;
      
          return <ChildComponent />;  // Unmounts and remounts every render
      };
      ```
      
      ### The Solution
      
      ```typescript
      // ✅ CORRECT - Define outside or use useMemo
      const ChildComponent: React.FC = () => <div>Child</div>;
      
      export const Parent: React.FC = () => {
          return <ChildComponent />;  // Stable component
      };
      
      // ✅ OR if dynamic, use useMemo
      export const Parent: React.FC<{ config: Config }> = ({ config }) => {
          const DynamicComponent = useMemo(() => {
              return () => <div>{config.title}</div>;
          }, [config.title]);
      
          return <DynamicComponent />;
      };
      ```
      
      ---
      
      ## Lazy Loading Heavy Dependencies
      
      ### Code Splitting
      
      ```typescript
      // ❌ AVOID - Import heavy libraries at top level
      import jsPDF from 'jspdf';  // Large library loaded immediately
      import * as XLSX from 'xlsx';  // Large library loaded immediately
      
      // ✅ CORRECT - Dynamic import when needed
      const handleExportPDF = async () => {
          const { jsPDF } = await import('jspdf');
          const doc = new jsPDF();
          // Use it
      };
      
      const handleExportExcel = async () => {
          const XLSX = await import('xlsx');
          // Use it
      };
      ```
      
      ---
      
      ## Summary
      
      **Performance Checklist:**
      - ✅ `useMemo` for expensive computations (filter, sort, map)
      - ✅ `useCallback` for functions passed to children
      - ✅ `React.memo` for expensive components
      - ✅ Debounce search/filter (300-500ms)
      - ✅ Cleanup timeouts/intervals in useEffect
      - ✅ Watch specific form fields (not all)
      - ✅ Stable keys in lists
      - ✅ Lazy load heavy libraries
      - ✅ Code splitting with React.lazy
      
      **See Also:**
      - [component-patterns.md](component-patterns.md) - Lazy loading
      - [data-fetching.md](data-fetching.md) - TanStack Query optimization
      - [complete-examples.md](complete-examples.md) - Performance patterns in context
    • routing-guide.md 7 KB
      # Routing Guide
      
      TanStack Router implementation with folder-based routing and lazy loading patterns.
      
      ---
      
      ## TanStack Router Overview
      
      **TanStack Router** with file-based routing:
      - Folder structure defines routes
      - Lazy loading for code splitting
      - Type-safe routing
      - Breadcrumb loaders
      
      ---
      
      ## Folder-Based Routing
      
      ### Directory Structure
      
      ```
      routes/
        __root.tsx                    # Root layout
        index.tsx                     # Home route (/)
        posts/
          index.tsx                   # /posts
          create/
            index.tsx                 # /posts/create
          $postId.tsx                 # /posts/:postId (dynamic)
        comments/
          index.tsx                   # /comments
      ```
      
      **Pattern**:
      - `index.tsx` = Route at that path
      - `$param.tsx` = Dynamic parameter
      - Nested folders = Nested routes
      
      ---
      
      ## Basic Route Pattern
      
      ### Example from posts/index.tsx
      
      ```typescript
      /**
       * Posts route component
       * Displays the main blog posts list
       */
      
      import { createFileRoute } from '@tanstack/react-router';
      import { lazy } from 'react';
      
      // Lazy load the page component
      const PostsList = lazy(() =>
          import('@/features/posts/components/PostsList').then(
              (module) => ({ default: module.PostsList }),
          ),
      );
      
      export const Route = createFileRoute('/posts/')({
          component: PostsPage,
          // Define breadcrumb data
          loader: () => ({
              crumb: 'Posts',
          }),
      });
      
      function PostsPage() {
          return (
              <PostsList
                  title='All Posts'
                  showFilters={true}
              />
          );
      }
      
      export default PostsPage;
      ```
      
      **Key Points:**
      - Lazy load heavy components
      - `createFileRoute` with route path
      - `loader` for breadcrumb data
      - Page component renders content
      - Export both Route and component
      
      ---
      
      ## Lazy Loading Routes
      
      ### Named Export Pattern
      
      ```typescript
      import { lazy } from 'react';
      
      // For named exports, use .then() to map to default
      const MyPage = lazy(() =>
          import('@/features/my-feature/components/MyPage').then(
              (module) => ({ default: module.MyPage })
          )
      );
      ```
      
      ### Default Export Pattern
      
      ```typescript
      import { lazy } from 'react';
      
      // For default exports, simpler syntax
      const MyPage = lazy(() => import('@/features/my-feature/components/MyPage'));
      ```
      
      ### Why Lazy Load Routes?
      
      - Code splitting - smaller initial bundle
      - Faster initial page load
      - Load route code only when navigated to
      - Better performance
      
      ---
      
      ## createFileRoute
      
      ### Basic Configuration
      
      ```typescript
      export const Route = createFileRoute('/my-route/')({
          component: MyRoutePage,
      });
      
      function MyRoutePage() {
          return <div>My Route Content</div>;
      }
      ```
      
      ### With Breadcrumb Loader
      
      ```typescript
      export const Route = createFileRoute('/my-route/')({
          component: MyRoutePage,
          loader: () => ({
              crumb: 'My Route Title',
          }),
      });
      ```
      
      Breadcrumb appears in navigation/app bar automatically.
      
      ### With Data Loader
      
      ```typescript
      export const Route = createFileRoute('/my-route/')({
          component: MyRoutePage,
          loader: async () => {
              // Can prefetch data here
              const data = await api.getData();
              return { crumb: 'My Route', data };
          },
      });
      ```
      
      ### With Search Params
      
      ```typescript
      export const Route = createFileRoute('/search/')({
          component: SearchPage,
          validateSearch: (search: Record<string, unknown>) => {
              return {
                  query: (search.query as string) || '',
                  page: Number(search.page) || 1,
              };
          },
      });
      
      function SearchPage() {
          const { query, page } = Route.useSearch();
          // Use query and page
      }
      ```
      
      ---
      
      ## Dynamic Routes
      
      ### Parameter Routes
      
      ```typescript
      // routes/users/$userId.tsx
      
      export const Route = createFileRoute('/users/$userId')({
          component: UserPage,
      });
      
      function UserPage() {
          const { userId } = Route.useParams();
      
          return <UserProfile userId={userId} />;
      }
      ```
      
      ### Multiple Parameters
      
      ```typescript
      // routes/posts/$postId/comments/$commentId.tsx
      
      export const Route = createFileRoute('/posts/$postId/comments/$commentId')({
          component: CommentPage,
      });
      
      function CommentPage() {
          const { postId, commentId } = Route.useParams();
      
          return <CommentEditor postId={postId} commentId={commentId} />;
      }
      ```
      
      ---
      
      ## Navigation
      
      ### Programmatic Navigation
      
      ```typescript
      import { useNavigate } from '@tanstack/react-router';
      
      export const MyComponent: React.FC = () => {
          const navigate = useNavigate();
      
          const handleClick = () => {
              navigate({ to: '/posts' });
          };
      
          return <Button onClick={handleClick}>View Posts</Button>;
      };
      ```
      
      ### With Parameters
      
      ```typescript
      const handleNavigate = () => {
          navigate({
              to: '/users/$userId',
              params: { userId: '123' },
          });
      };
      ```
      
      ### With Search Params
      
      ```typescript
      const handleSearch = () => {
          navigate({
              to: '/search',
              search: { query: 'test', page: 1 },
          });
      };
      ```
      
      ---
      
      ## Route Layout Pattern
      
      ### Root Layout (__root.tsx)
      
      ```typescript
      import { createRootRoute, Outlet } from '@tanstack/react-router';
      import { Box } from '@mui/material';
      import { CustomAppBar } from '~components/CustomAppBar';
      
      export const Route = createRootRoute({
          component: RootLayout,
      });
      
      function RootLayout() {
          return (
              <Box>
                  <CustomAppBar />
                  <Box sx={{ p: 2 }}>
                      <Outlet />  {/* Child routes render here */}
                  </Box>
              </Box>
          );
      }
      ```
      
      ### Nested Layouts
      
      ```typescript
      // routes/dashboard/index.tsx
      export const Route = createFileRoute('/dashboard/')({
          component: DashboardLayout,
      });
      
      function DashboardLayout() {
          return (
              <Box>
                  <DashboardSidebar />
                  <Box sx={{ flex: 1 }}>
                      <Outlet />  {/* Nested routes */}
                  </Box>
              </Box>
          );
      }
      ```
      
      ---
      
      ## Complete Route Example
      
      ```typescript
      /**
       * User profile route
       * Path: /users/:userId
       */
      
      import { createFileRoute } from '@tanstack/react-router';
      import { lazy } from 'react';
      import { SuspenseLoader } from '~components/SuspenseLoader';
      
      // Lazy load heavy component
      const UserProfile = lazy(() =>
          import('@/features/users/components/UserProfile').then(
              (module) => ({ default: module.UserProfile })
          )
      );
      
      export const Route = createFileRoute('/users/$userId')({
          component: UserPage,
          loader: () => ({
              crumb: 'User Profile',
          }),
      });
      
      function UserPage() {
          const { userId } = Route.useParams();
      
          return (
              <SuspenseLoader>
                  <UserProfile userId={userId} />
              </SuspenseLoader>
          );
      }
      
      export default UserPage;
      ```
      
      ---
      
      ## Summary
      
      **Routing Checklist:**
      - ✅ Folder-based: `routes/my-route/index.tsx`
      - ✅ Lazy load components: `React.lazy(() => import())`
      - ✅ Use `createFileRoute` with route path
      - ✅ Add breadcrumb in `loader` function
      - ✅ Wrap in `SuspenseLoader` for loading states
      - ✅ Use `Route.useParams()` for dynamic params
      - ✅ Use `useNavigate()` for programmatic navigation
      
      **See Also:**
      - [component-patterns.md](component-patterns.md) - Lazy loading patterns
      - [loading-and-error-states.md](loading-and-error-states.md) - SuspenseLoader usage
      - [complete-examples.md](complete-examples.md) - Full route examples
    • styling-guide.md 7.7 KB
      # Styling Guide
      
      Modern styling patterns for using MUI v7 sx prop, inline styles, and theme integration.
      
      ---
      
      ## Inline vs Separate Styles
      
      ### Decision Threshold
      
      **<100 lines: Inline styles at top of component**
      
      ```typescript
      import type { SxProps, Theme } from '@mui/material';
      
      const componentStyles: Record<string, SxProps<Theme>> = {
          container: {
              p: 2,
              display: 'flex',
              flexDirection: 'column',
          },
          header: {
              mb: 2,
              borderBottom: '1px solid',
              borderColor: 'divider',
          },
          // ... more styles
      };
      
      export const MyComponent: React.FC = () => {
          return (
              <Box sx={componentStyles.container}>
                  <Box sx={componentStyles.header}>
                      <h2>Title</h2>
                  </Box>
              </Box>
          );
      };
      ```
      
      **>100 lines: Separate `.styles.ts` file**
      
      ```typescript
      // MyComponent.styles.ts
      import type { SxProps, Theme } from '@mui/material';
      
      export const componentStyles: Record<string, SxProps<Theme>> = {
          container: { ... },
          header: { ... },
          // ... 100+ lines of styles
      };
      
      // MyComponent.tsx
      import { componentStyles } from './MyComponent.styles';
      
      export const MyComponent: React.FC = () => {
          return <Box sx={componentStyles.container}>...</Box>;
      };
      ```
      
      ### Real Example: UnifiedForm.tsx
      
      **Lines 48-126**: 78 lines of inline styles (acceptable)
      
      ```typescript
      const formStyles: Record<string, SxProps<Theme>> = {
          gridContainer: {
              height: '100%',
              maxHeight: 'calc(100vh - 220px)',
          },
          section: {
              height: '100%',
              maxHeight: 'calc(100vh - 220px)',
              overflow: 'auto',
              p: 4,
          },
          // ... 15 more style objects
      };
      ```
      
      **Guideline**: User is comfortable with ~80 lines inline. Use your judgment around 100 lines.
      
      ---
      
      ## sx Prop Patterns
      
      ### Basic Usage
      
      ```typescript
      <Box sx={{ p: 2, mb: 3, display: 'flex' }}>
          Content
      </Box>
      ```
      
      ### With Theme Access
      
      ```typescript
      <Box
          sx={{
              p: 2,
              backgroundColor: (theme) => theme.palette.primary.main,
              color: (theme) => theme.palette.primary.contrastText,
              borderRadius: (theme) => theme.shape.borderRadius,
          }}
      >
          Themed Box
      </Box>
      ```
      
      ### Responsive Styles
      
      ```typescript
      <Box
          sx={{
              p: { xs: 1, sm: 2, md: 3 },
              width: { xs: '100%', md: '50%' },
              flexDirection: { xs: 'column', md: 'row' },
          }}
      >
          Responsive Layout
      </Box>
      ```
      
      ### Pseudo-Selectors
      
      ```typescript
      <Box
          sx={{
              p: 2,
              '&:hover': {
                  backgroundColor: 'rgba(0,0,0,0.05)',
              },
              '&:active': {
                  backgroundColor: 'rgba(0,0,0,0.1)',
              },
              '& .child-class': {
                  color: 'primary.main',
              },
          }}
      >
          Interactive Box
      </Box>
      ```
      
      ---
      
      ## MUI v7 Patterns
      
      ### Grid Component (v7 Syntax)
      
      ```typescript
      import { Grid } from '@mui/material';
      
      // ✅ CORRECT - v7 syntax with size prop
      <Grid container spacing={2}>
          <Grid size={{ xs: 12, md: 6 }}>
              Left Column
          </Grid>
          <Grid size={{ xs: 12, md: 6 }}>
              Right Column
          </Grid>
      </Grid>
      
      // ❌ WRONG - Old v6 syntax
      <Grid container spacing={2}>
          <Grid xs={12} md={6}>  {/* OLD - Don't use */}
              Content
          </Grid>
      </Grid>
      ```
      
      **Key Change**: `size={{ xs: 12, md: 6 }}` instead of `xs={12} md={6}`
      
      ### Responsive Grid
      
      ```typescript
      <Grid container spacing={3}>
          <Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }}>
              Responsive Column
          </Grid>
      </Grid>
      ```
      
      ### Nested Grids
      
      ```typescript
      <Grid container spacing={2}>
          <Grid size={{ xs: 12, md: 8 }}>
              <Grid container spacing={1}>
                  <Grid size={{ xs: 12, sm: 6 }}>
                      Nested 1
                  </Grid>
                  <Grid size={{ xs: 12, sm: 6 }}>
                      Nested 2
                  </Grid>
              </Grid>
          </Grid>
      
          <Grid size={{ xs: 12, md: 4 }}>
              Sidebar
          </Grid>
      </Grid>
      ```
      
      ---
      
      ## Type-Safe Styles
      
      ### Style Object Type
      
      ```typescript
      import type { SxProps, Theme } from '@mui/material';
      
      // Type-safe styles
      const styles: Record<string, SxProps<Theme>> = {
          container: {
              p: 2,
              // Autocomplete and type checking work here
          },
      };
      
      // Or individual style
      const containerStyle: SxProps<Theme> = {
          p: 2,
          display: 'flex',
      };
      ```
      
      ### Theme-Aware Styles
      
      ```typescript
      const styles: Record<string, SxProps<Theme>> = {
          primary: {
              color: (theme) => theme.palette.primary.main,
              backgroundColor: (theme) => theme.palette.primary.light,
              '&:hover': {
                  backgroundColor: (theme) => theme.palette.primary.dark,
              },
          },
          customSpacing: {
              padding: (theme) => theme.spacing(2),
              margin: (theme) => theme.spacing(1, 2), // top/bottom: 1, left/right: 2
          },
      };
      ```
      
      ---
      
      ## What NOT to Use
      
      ### ❌ makeStyles (MUI v4 pattern)
      
      ```typescript
      // ❌ AVOID - Old Material-UI v4 pattern
      import { makeStyles } from '@mui/styles';
      
      const useStyles = makeStyles((theme) => ({
          root: {
              padding: theme.spacing(2),
          },
      }));
      ```
      
      **Why avoid**: Deprecated, v7 doesn't support it well
      
      ### ❌ styled() Components
      
      ```typescript
      // ❌ AVOID - styled-components pattern
      import { styled } from '@mui/material/styles';
      
      const StyledBox = styled(Box)(({ theme }) => ({
          padding: theme.spacing(2),
      }));
      ```
      
      **Why avoid**: sx prop is more flexible and doesn't create new components
      
      ### ✅ Use sx Prop Instead
      
      ```typescript
      // ✅ PREFERRED
      <Box
          sx={{
              p: 2,
              backgroundColor: 'primary.main',
          }}
      >
          Content
      </Box>
      ```
      
      ---
      
      ## Code Style Standards
      
      ### Indentation
      
      **4 spaces** (not 2, not tabs)
      
      ```typescript
      const styles: Record<string, SxProps<Theme>> = {
          container: {
              p: 2,
              display: 'flex',
              flexDirection: 'column',
          },
      };
      ```
      
      ### Quotes
      
      **Single quotes** for strings (project standard)
      
      ```typescript
      // ✅ CORRECT
      const color = 'primary.main';
      import { Box } from '@mui/material';
      
      // ❌ WRONG
      const color = "primary.main";
      import { Box } from "@mui/material";
      ```
      
      ### Trailing Commas
      
      **Always use trailing commas** in objects and arrays
      
      ```typescript
      // ✅ CORRECT
      const styles = {
          container: { p: 2 },
          header: { mb: 1 },  // Trailing comma
      };
      
      const items = [
          'item1',
          'item2',  // Trailing comma
      ];
      
      // ❌ WRONG - No trailing comma
      const styles = {
          container: { p: 2 },
          header: { mb: 1 }  // Missing comma
      };
      ```
      
      ---
      
      ## Common Style Patterns
      
      ### Flexbox Layout
      
      ```typescript
      const styles = {
          flexRow: {
              display: 'flex',
              flexDirection: 'row',
              alignItems: 'center',
              gap: 2,
          },
          flexColumn: {
              display: 'flex',
              flexDirection: 'column',
              gap: 1,
          },
          spaceBetween: {
              display: 'flex',
              justifyContent: 'space-between',
              alignItems: 'center',
          },
      };
      ```
      
      ### Spacing
      
      ```typescript
      // Padding
      p: 2           // All sides
      px: 2          // Horizontal (left + right)
      py: 2          // Vertical (top + bottom)
      pt: 2, pr: 1   // Specific sides
      
      // Margin
      m: 2, mx: 2, my: 2, mt: 2, mr: 1
      
      // Units: 1 = 8px (theme.spacing(1))
      p: 2  // = 16px
      p: 0.5  // = 4px
      ```
      
      ### Positioning
      
      ```typescript
      const styles = {
          relative: {
              position: 'relative',
          },
          absolute: {
              position: 'absolute',
              top: 0,
              right: 0,
          },
          sticky: {
              position: 'sticky',
              top: 0,
              zIndex: 1000,
          },
      };
      ```
      
      ---
      
      ## Summary
      
      **Styling Checklist:**
      - ✅ Use `sx` prop for MUI styling
      - ✅ Type-safe with `SxProps<Theme>`
      - ✅ <100 lines: inline; >100 lines: separate file
      - ✅ MUI v7 Grid: `size={{ xs: 12 }}`
      - ✅ 4 space indentation
      - ✅ Single quotes
      - ✅ Trailing commas
      - ❌ No makeStyles or styled()
      
      **See Also:**
      - [component-patterns.md](component-patterns.md) - Component structure
      - [complete-examples.md](complete-examples.md) - Full styling examples
    • typescript-standards.md 8.2 KB
      # TypeScript Standards
      
      TypeScript best practices for type safety and maintainability in React frontend code.
      
      ---
      
      ## Strict Mode
      
      ### Configuration
      
      TypeScript strict mode is **enabled** in the project:
      
      ```json
      // tsconfig.json
      {
          "compilerOptions": {
              "strict": true,
              "noImplicitAny": true,
              "strictNullChecks": true
          }
      }
      ```
      
      **This means:**
      - No implicit `any` types
      - Null/undefined must be handled explicitly
      - Type safety enforced
      
      ---
      
      ## No `any` Type
      
      ### The Rule
      
      ```typescript
      // ❌ NEVER use any
      function handleData(data: any) {
          return data.something;
      }
      
      // ✅ Use specific types
      interface MyData {
          something: string;
      }
      
      function handleData(data: MyData) {
          return data.something;
      }
      
      // ✅ Or use unknown for truly unknown data
      function handleUnknown(data: unknown) {
          if (typeof data === 'object' && data !== null && 'something' in data) {
              return (data as MyData).something;
          }
      }
      ```
      
      **If you truly don't know the type:**
      - Use `unknown` (forces type checking)
      - Use type guards to narrow
      - Document why type is unknown
      
      ---
      
      ## Explicit Return Types
      
      ### Function Return Types
      
      ```typescript
      // ✅ CORRECT - Explicit return type
      function getUser(id: number): Promise<User> {
          return apiClient.get(`/users/${id}`);
      }
      
      function calculateTotal(items: Item[]): number {
          return items.reduce((sum, item) => sum + item.price, 0);
      }
      
      // ❌ AVOID - Implicit return type (less clear)
      function getUser(id: number) {
          return apiClient.get(`/users/${id}`);
      }
      ```
      
      ### Component Return Types
      
      ```typescript
      // React.FC already provides return type (ReactElement)
      export const MyComponent: React.FC<Props> = ({ prop }) => {
          return <div>{prop}</div>;
      };
      
      // For custom hooks
      function useMyData(id: number): { data: Data; isLoading: boolean } {
          const [data, setData] = useState<Data | null>(null);
          const [isLoading, setIsLoading] = useState(true);
      
          return { data: data!, isLoading };
      }
      ```
      
      ---
      
      ## Type Imports
      
      ### Use 'type' Keyword
      
      ```typescript
      // ✅ CORRECT - Explicitly mark as type import
      import type { User } from '~types/user';
      import type { Post } from '~types/post';
      import type { SxProps, Theme } from '@mui/material';
      
      // ❌ AVOID - Mixed value and type imports
      import { User } from '~types/user';  // Unclear if type or value
      ```
      
      **Benefits:**
      - Clearly separates types from values
      - Better tree-shaking
      - Prevents circular dependencies
      - TypeScript compiler optimization
      
      ---
      
      ## Component Prop Interfaces
      
      ### Interface Pattern
      
      ```typescript
      /**
       * Props for MyComponent
       */
      interface MyComponentProps {
          /** The user ID to display */
          userId: number;
      
          /** Optional callback when action completes */
          onComplete?: () => void;
      
          /** Display mode for the component */
          mode?: 'view' | 'edit';
      
          /** Additional CSS classes */
          className?: string;
      }
      
      export const MyComponent: React.FC<MyComponentProps> = ({
          userId,
          onComplete,
          mode = 'view',  // Default value
          className,
      }) => {
          return <div>...</div>;
      };
      ```
      
      **Key Points:**
      - Separate interface for props
      - JSDoc comments for each prop
      - Optional props use `?`
      - Provide defaults in destructuring
      
      ### Props with Children
      
      ```typescript
      interface ContainerProps {
          children: React.ReactNode;
          title: string;
      }
      
      // React.FC automatically includes children type, but be explicit
      export const Container: React.FC<ContainerProps> = ({ children, title }) => {
          return (
              <div>
                  <h2>{title}</h2>
                  {children}
              </div>
          );
      };
      ```
      
      ---
      
      ## Utility Types
      
      ### Partial<T>
      
      ```typescript
      // Make all properties optional
      type UserUpdate = Partial<User>;
      
      function updateUser(id: number, updates: Partial<User>) {
          // updates can have any subset of User properties
      }
      ```
      
      ### Pick<T, K>
      
      ```typescript
      // Select specific properties
      type UserPreview = Pick<User, 'id' | 'name' | 'email'>;
      
      const preview: UserPreview = {
          id: 1,
          name: 'John',
          email: 'john@example.com',
          // Other User properties not allowed
      };
      ```
      
      ### Omit<T, K>
      
      ```typescript
      // Exclude specific properties
      type UserWithoutPassword = Omit<User, 'password' | 'passwordHash'>;
      
      const publicUser: UserWithoutPassword = {
          id: 1,
          name: 'John',
          email: 'john@example.com',
          // password and passwordHash not allowed
      };
      ```
      
      ### Required<T>
      
      ```typescript
      // Make all properties required
      type RequiredConfig = Required<Config>;  // All optional props become required
      ```
      
      ### Record<K, V>
      
      ```typescript
      // Type-safe object/map
      const userMap: Record<string, User> = {
          'user1': { id: 1, name: 'John' },
          'user2': { id: 2, name: 'Jane' },
      };
      
      // For styles
      import type { SxProps, Theme } from '@mui/material';
      
      const styles: Record<string, SxProps<Theme>> = {
          container: { p: 2 },
          header: { mb: 1 },
      };
      ```
      
      ---
      
      ## Type Guards
      
      ### Basic Type Guards
      
      ```typescript
      function isUser(data: unknown): data is User {
          return (
              typeof data === 'object' &&
              data !== null &&
              'id' in data &&
              'name' in data
          );
      }
      
      // Usage
      if (isUser(response)) {
          console.log(response.name);  // TypeScript knows it's User
      }
      ```
      
      ### Discriminated Unions
      
      ```typescript
      type LoadingState =
          | { status: 'idle' }
          | { status: 'loading' }
          | { status: 'success'; data: Data }
          | { status: 'error'; error: Error };
      
      function Component({ state }: { state: LoadingState }) {
          // TypeScript narrows type based on status
          if (state.status === 'success') {
              return <Display data={state.data} />;  // data available here
          }
      
          if (state.status === 'error') {
              return <Error error={state.error} />;  // error available here
          }
      
          return <Loading />;
      }
      ```
      
      ---
      
      ## Generic Types
      
      ### Generic Functions
      
      ```typescript
      function getById<T>(items: T[], id: number): T | undefined {
          return items.find(item => (item as any).id === id);
      }
      
      // Usage with type inference
      const users: User[] = [...];
      const user = getById(users, 123);  // Type: User | undefined
      ```
      
      ### Generic Components
      
      ```typescript
      interface ListProps<T> {
          items: T[];
          renderItem: (item: T) => React.ReactNode;
      }
      
      export function List<T>({ items, renderItem }: ListProps<T>): React.ReactElement {
          return (
              <div>
                  {items.map((item, index) => (
                      <div key={index}>{renderItem(item)}</div>
                  ))}
              </div>
          );
      }
      
      // Usage
      <List<User>
          items={users}
          renderItem={(user) => <UserCard user={user} />}
      />
      ```
      
      ---
      
      ## Type Assertions (Use Sparingly)
      
      ### When to Use
      
      ```typescript
      // ✅ OK - When you know more than TypeScript
      const element = document.getElementById('my-element') as HTMLInputElement;
      const value = element.value;
      
      // ✅ OK - API response that you've validated
      const response = await api.getData();
      const user = response.data as User;  // You know the shape
      ```
      
      ### When NOT to Use
      
      ```typescript
      // ❌ AVOID - Circumventing type safety
      const data = getData() as any;  // WRONG - defeats TypeScript
      
      // ❌ AVOID - Unsafe assertion
      const value = unknownValue as string;  // Might not actually be string
      ```
      
      ---
      
      ## Null/Undefined Handling
      
      ### Optional Chaining
      
      ```typescript
      // ✅ CORRECT
      const name = user?.profile?.name;
      
      // Equivalent to:
      const name = user && user.profile && user.profile.name;
      ```
      
      ### Nullish Coalescing
      
      ```typescript
      // ✅ CORRECT
      const displayName = user?.name ?? 'Anonymous';
      
      // Only uses default if null or undefined
      // (Different from || which triggers on '', 0, false)
      ```
      
      ### Non-Null Assertion (Use Carefully)
      
      ```typescript
      // ✅ OK - When you're certain value exists
      const data = queryClient.getQueryData<Data>(['data'])!;
      
      // ⚠️ CAREFUL - Only use when you KNOW it's not null
      // Better to check explicitly:
      const data = queryClient.getQueryData<Data>(['data']);
      if (data) {
          // Use data
      }
      ```
      
      ---
      
      ## Summary
      
      **TypeScript Checklist:**
      - ✅ Strict mode enabled
      - ✅ No `any` type (use `unknown` if needed)
      - ✅ Explicit return types on functions
      - ✅ Use `import type` for type imports
      - ✅ JSDoc comments on prop interfaces
      - ✅ Utility types (Partial, Pick, Omit, Required, Record)
      - ✅ Type guards for narrowing
      - ✅ Optional chaining and nullish coalescing
      - ❌ Avoid type assertions unless necessary
      
      **See Also:**
      - [component-patterns.md](component-patterns.md) - Component typing
      - [data-fetching.md](data-fetching.md) - API typing
  • SKILL.md 11.5 KB
    ---
    name: frontend-dev-guidelines
    description: Frontend development guidelines for React/TypeScript applications. Modern patterns including Suspense, lazy loading, useSuspenseQuery, file organization with features directory, MUI v7 styling, TanStack Router, performance optimization, and TypeScript best practices. Use when creating components, pages, features, fetching data, styling, routing, or working with frontend code.
    ---
    
    # Frontend Development Guidelines
    
    ## Purpose
    
    Comprehensive guide for modern React development, emphasizing Suspense-based data fetching, lazy loading, proper file organization, and performance optimization.
    
    ## When to Use This Skill
    
    - Creating new components or pages
    - Building new features
    - Fetching data with TanStack Query
    - Setting up routing with TanStack Router
    - Styling components with MUI v7
    - Performance optimization
    - Organizing frontend code
    - TypeScript best practices
    
    ---
    
    ## Quick Start
    
    ### New Component Checklist
    
    Creating a component? Follow this checklist:
    
    - [ ] Use `React.FC<Props>` pattern with TypeScript
    - [ ] Lazy load if heavy component: `React.lazy(() => import())`
    - [ ] Wrap in `<SuspenseLoader>` for loading states
    - [ ] Use `useSuspenseQuery` for data fetching
    - [ ] Import aliases: `@/`, `~types`, `~components`, `~features`
    - [ ] Styles: Inline if <100 lines, separate file if >100 lines
    - [ ] Use `useCallback` for event handlers passed to children
    - [ ] Default export at bottom
    - [ ] No early returns with loading spinners
    - [ ] Use `useMuiSnackbar` for user notifications
    
    ### New Feature Checklist
    
    Creating a feature? Set up this structure:
    
    - [ ] Create `features/{feature-name}/` directory
    - [ ] Create subdirectories: `api/`, `components/`, `hooks/`, `helpers/`, `types/`
    - [ ] Create API service file: `api/{feature}Api.ts`
    - [ ] Set up TypeScript types in `types/`
    - [ ] Create route in `routes/{feature-name}/index.tsx`
    - [ ] Lazy load feature components
    - [ ] Use Suspense boundaries
    - [ ] Export public API from feature `index.ts`
    
    ---
    
    ## Import Aliases Quick Reference
    
    | Alias | Resolves To | Example |
    |-------|-------------|---------|
    | `@/` | `src/` | `import { apiClient } from '@/lib/apiClient'` |
    | `~types` | `src/types` | `import type { User } from '~types/user'` |
    | `~components` | `src/components` | `import { SuspenseLoader } from '~components/SuspenseLoader'` |
    | `~features` | `src/features` | `import { authApi } from '~features/auth'` |
    
    Defined in: [vite.config.ts](../../vite.config.ts) lines 180-185
    
    ---
    
    ## Common Imports Cheatsheet
    
    ```typescript
    // React & Lazy Loading
    import React, { useState, useCallback, useMemo } from 'react';
    const Heavy = React.lazy(() => import('./Heavy'));
    
    // MUI Components
    import { Box, Paper, Typography, Button, Grid } from '@mui/material';
    import type { SxProps, Theme } from '@mui/material';
    
    // TanStack Query (Suspense)
    import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query';
    
    // TanStack Router
    import { createFileRoute } from '@tanstack/react-router';
    
    // Project Components
    import { SuspenseLoader } from '~components/SuspenseLoader';
    
    // Hooks
    import { useAuth } from '@/hooks/useAuth';
    import { useMuiSnackbar } from '@/hooks/useMuiSnackbar';
    
    // Types
    import type { Post } from '~types/post';
    ```
    
    ---
    
    ## Topic Guides
    
    ### 🎨 Component Patterns
    
    **Modern React components use:**
    - `React.FC<Props>` for type safety
    - `React.lazy()` for code splitting
    - `SuspenseLoader` for loading states
    - Named const + default export pattern
    
    **Key Concepts:**
    - Lazy load heavy components (DataGrid, charts, editors)
    - Always wrap lazy components in Suspense
    - Use SuspenseLoader component (with fade animation)
    - Component structure: Props → Hooks → Handlers → Render → Export
    
    **CRITICAL RULE: UI RESEARCH HARD-GATE**
    Before creating any user-facing component or page, you MUST run a Subagent to research real-world UI patterns.
    - Call `invoke_subagent` (research) with instructions to search Awwwards/ThreeUI for specific context keywords (e.g., "SaaS Dashboard").
    - Do not invent color palettes or layouts blindly. Use the exact Tailwind/MUI theme variables returned by the Subagent's visual analysis report.
    
    **[📖 Complete Guide: resources/component-patterns.md](resources/component-patterns.md)**
    
    ---
    
    ### 📊 Data Fetching
    
    **PRIMARY PATTERN: useSuspenseQuery**
    - Use with Suspense boundaries
    - Cache-first strategy (check grid cache before API)
    - Replaces `isLoading` checks
    - Type-safe with generics
    
    **API Service Layer:**
    - Create `features/{feature}/api/{feature}Api.ts`
    - Use `apiClient` axios instance
    - Centralized methods per feature
    - Route format: `/form/route` (NOT `/api/form/route`)
    
    **[📖 Complete Guide: resources/data-fetching.md](resources/data-fetching.md)**
    
    ---
    
    ### 📁 File Organization
    
    **features/ vs components/:**
    - `features/`: Domain-specific (posts, comments, auth)
    - `components/`: Truly reusable (SuspenseLoader, CustomAppBar)
    
    **Feature Subdirectories:**
    ```
    features/
      my-feature/
        api/          # API service layer
        components/   # Feature components
        hooks/        # Custom hooks
        helpers/      # Utility functions
        types/        # TypeScript types
    ```
    
    **[📖 Complete Guide: resources/file-organization.md](resources/file-organization.md)**
    
    ---
    
    ### 🎨 Styling
    
    **Inline vs Separate:**
    - <100 lines: Inline `const styles: Record<string, SxProps<Theme>>`
    - >100 lines: Separate `.styles.ts` file
    
    **Primary Method:**
    - Use `sx` prop for MUI components
    - Type-safe with `SxProps<Theme>`
    - Theme access: `(theme) => theme.palette.primary.main`
    
    **MUI v7 Grid:**
    ```typescript
    <Grid size={{ xs: 12, md: 6 }}>  // ✅ v7 syntax
    <Grid xs={12} md={6}>             // ❌ Old syntax
    ```
    
    **[📖 Complete Guide: resources/styling-guide.md](resources/styling-guide.md)**
    
    ---
    
    ### 🛣️ Routing
    
    **TanStack Router - Folder-Based:**
    - Directory: `routes/my-route/index.tsx`
    - Lazy load components
    - Use `createFileRoute`
    - Breadcrumb data in loader
    
    **Example:**
    ```typescript
    import { createFileRoute } from '@tanstack/react-router';
    import { lazy } from 'react';
    
    const MyPage = lazy(() => import('@/features/my-feature/components/MyPage'));
    
    export const Route = createFileRoute('/my-route/')({
        component: MyPage,
        loader: () => ({ crumb: 'My Route' }),
    });
    ```
    
    **[📖 Complete Guide: resources/routing-guide.md](resources/routing-guide.md)**
    
    ---
    
    ### ⏳ Loading & Error States
    
    **CRITICAL RULE: No Early Returns**
    
    ```typescript
    // ❌ NEVER - Causes layout shift
    if (isLoading) {
        return <LoadingSpinner />;
    }
    
    // ✅ ALWAYS - Consistent layout
    <SuspenseLoader>
        <Content />
    </SuspenseLoader>
    ```
    
    **Why:** Prevents Cumulative Layout Shift (CLS), better UX
    
    **Error Handling:**
    - Use `useMuiSnackbar` for user feedback
    - NEVER `react-toastify`
    - TanStack Query `onError` callbacks
    
    **[📖 Complete Guide: resources/loading-and-error-states.md](resources/loading-and-error-states.md)**
    
    ---
    
    ### ⚡ Performance
    
    **Optimization Patterns:**
    - `useMemo`: Expensive computations (filter, sort, map)
    - `useCallback`: Event handlers passed to children
    - `React.memo`: Expensive components
    - Debounced search (300-500ms)
    - Memory leak prevention (cleanup in useEffect)
    
    **[📖 Complete Guide: resources/performance.md](resources/performance.md)**
    
    ---
    
    ### 📘 TypeScript
    
    **Standards:**
    - Strict mode, no `any` type
    - Explicit return types on functions
    - Type imports: `import type { User } from '~types/user'`
    - Component prop interfaces with JSDoc
    
    **[📖 Complete Guide: resources/typescript-standards.md](resources/typescript-standards.md)**
    
    ---
    
    ### 🔧 Common Patterns
    
    **Covered Topics:**
    - React Hook Form with Zod validation
    - DataGrid wrapper contracts
    - Dialog component standards
    - `useAuth` hook for current user
    - Mutation patterns with cache invalidation
    
    **[📖 Complete Guide: resources/common-patterns.md](resources/common-patterns.md)**
    
    ---
    
    ### 📚 Complete Examples
    
    **Full working examples:**
    - Modern component with all patterns
    - Complete feature structure
    - API service layer
    - Route with lazy loading
    - Suspense + useSuspenseQuery
    - Form with validation
    
    **[📖 Complete Guide: resources/complete-examples.md](resources/complete-examples.md)**
    
    ---
    
    ## Navigation Guide
    
    | Need to... | Read this resource |
    |------------|-------------------|
    | Create a component | [component-patterns.md](resources/component-patterns.md) |
    | Fetch data | [data-fetching.md](resources/data-fetching.md) |
    | Organize files/folders | [file-organization.md](resources/file-organization.md) |
    | Style components | [styling-guide.md](resources/styling-guide.md) |
    | Set up routing | [routing-guide.md](resources/routing-guide.md) |
    | Handle loading/errors | [loading-and-error-states.md](resources/loading-and-error-states.md) |
    | Optimize performance | [performance.md](resources/performance.md) |
    | TypeScript types | [typescript-standards.md](resources/typescript-standards.md) |
    | Forms/Auth/DataGrid | [common-patterns.md](resources/common-patterns.md) |
    | See full examples | [complete-examples.md](resources/complete-examples.md) |
    
    ---
    
    ## Core Principles
    
    1. **Lazy Load Everything Heavy**: Routes, DataGrid, charts, editors
    2. **Suspense for Loading**: Use SuspenseLoader, not early returns
    3. **useSuspenseQuery**: Primary data fetching pattern for new code
    4. **Features are Organized**: api/, components/, hooks/, helpers/ subdirs
    5. **Styles Based on Size**: <100 inline, >100 separate
    6. **Import Aliases**: Use @/, ~types, ~components, ~features
    7. **No Early Returns**: Prevents layout shift
    8. **useMuiSnackbar**: For all user notifications
    
    ---
    
    ## Quick Reference: File Structure
    
    ```
    src/
      features/
        my-feature/
          api/
            myFeatureApi.ts       # API service
          components/
            MyFeature.tsx         # Main component
            SubComponent.tsx      # Related components
          hooks/
            useMyFeature.ts       # Custom hooks
            useSuspenseMyFeature.ts  # Suspense hooks
          helpers/
            myFeatureHelpers.ts   # Utilities
          types/
            index.ts              # TypeScript types
          index.ts                # Public exports
    
      components/
        SuspenseLoader/
          SuspenseLoader.tsx      # Reusable loader
        CustomAppBar/
          CustomAppBar.tsx        # Reusable app bar
    
      routes/
        my-route/
          index.tsx               # Route component
          create/
            index.tsx             # Nested route
    ```
    
    ---
    
    ## Modern Component Template (Quick Copy)
    
    ```typescript
    import React, { useState, useCallback } from 'react';
    import { Box, Paper } from '@mui/material';
    import { useSuspenseQuery } from '@tanstack/react-query';
    import { featureApi } from '../api/featureApi';
    import type { FeatureData } from '~types/feature';
    
    interface MyComponentProps {
        id: number;
        onAction?: () => void;
    }
    
    export const MyComponent: React.FC<MyComponentProps> = ({ id, onAction }) => {
        const [state, setState] = useState<string>('');
    
        const { data } = useSuspenseQuery({
            queryKey: ['feature', id],
            queryFn: () => featureApi.getFeature(id),
        });
    
        const handleAction = useCallback(() => {
            setState('updated');
            onAction?.();
        }, [onAction]);
    
        return (
            <Box sx={{ p: 2 }}>
                <Paper sx={{ p: 3 }}>
                    {/* Content */}
                </Paper>
            </Box>
        );
    };
    
    export default MyComponent;
    ```
    
    For complete examples, see [resources/complete-examples.md](resources/complete-examples.md)
    
    ---
    
    ## Related Skills
    
    - **error-tracking**: Error tracking with Sentry (applies to frontend too)
    - **backend-dev-guidelines**: Backend API patterns that frontend consumes
    
    ---
    
    **Skill Status**: Modular structure with progressive loading for optimal context management

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related