# Loading States Pattern Guide

## FE-COMP-001: Add loading states to all async operations

This document outlines the patterns and best practices for adding loading states to async operations in the Veza frontend.

## Components Available

### 1. LoadingSpinner

Located at `@/components/ui/loading-spinner`

```tsx
import { LoadingSpinner } from '@/components/ui/loading-spinner';

<LoadingSpinner size="md" text="Loading..." />;
```

### 2. Skeleton

Located at `@/components/ui/skeleton`

```tsx
import { Skeleton } from '@/components/ui/skeleton';

<Skeleton variant="rectangular" width="100%" height="200px" />;
```

### 3. ButtonLoading

Located at `@/components/ui/button-loading`

```tsx
import { ButtonLoading } from '@/components/ui/button-loading';

<ButtonLoading
  isLoading={isSubmitting}
  loadingText="Submitting..."
  onClick={handleSubmit}
>
  Submit
</ButtonLoading>;
```

## Patterns

### Pattern 1: Form Submissions

Always disable the submit button and show a loading indicator during submission:

```tsx
const [isSubmitting, setIsSubmitting] = useState(false);

const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  setIsSubmitting(true);
  try {
    await submitForm();
  } finally {
    setIsSubmitting(false);
  }
};

<Button type="submit" disabled={isSubmitting}>
  {isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
  Submit
</Button>;
```

### Pattern 2: Data Fetching with TanStack Query

Use `isLoading` from `useQuery`:

```tsx
const { data, isLoading, error } = useQuery({
  queryKey: ['key'],
  queryFn: fetchData,
});

if (isLoading) {
  return <LoadingSpinner />;
}
```

### Pattern 3: Mutations with TanStack Query

Use `isPending` from `useMutation`:

```tsx
const mutation = useMutation({
  mutationFn: updateData,
});

<Button onClick={() => mutation.mutate(data)} disabled={mutation.isPending}>
  {mutation.isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
  Update
</Button>;
```

### Pattern 4: Skeleton Loaders for Lists

Use skeleton loaders while data is loading:

```tsx
if (isLoading) {
  return (
    <div className="space-y-4">
      {Array.from({ length: 5 }).map((_, i) => (
        <Skeleton key={i} height="80px" />
      ))}
    </div>
  );
}
```

### Pattern 5: Inline Loading States

For operations that don't block the entire UI:

```tsx
<Button onClick={handleAction} disabled={isLoading}>
  {isLoading ? (
    <>
      <Loader2 className="mr-2 h-4 w-4 animate-spin" />
      Processing...
    </>
  ) : (
    'Action'
  )}
</Button>
```

## Checklist

When implementing async operations, ensure:

- [ ] Button is disabled during operation
- [ ] Loading indicator is visible (spinner or skeleton)
- [ ] Loading text/state is clear to user
- [ ] Error states are handled
- [ ] Success states provide feedback
- [ ] Form inputs are disabled during submission (if applicable)
- [ ] Navigation is prevented during critical operations

## Examples in Codebase

- `PlaylistForm.tsx` - Form submission with loading state
- `FollowButton.tsx` - Inline loading state
- `AddCollaboratorModal.tsx` - Mutation with loading state
- `NotificationsPage.tsx` - Query with loading state
- `SearchPage.tsx` - Multiple queries with loading states
