3.2 KiB
3.2 KiB
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
import { LoadingSpinner } from '@/components/ui/loading-spinner';
<LoadingSpinner size="md" text="Loading..." />;
2. Skeleton
Located at @/components/ui/skeleton
import { Skeleton } from '@/components/ui/skeleton';
<Skeleton variant="rectangular" width="100%" height="200px" />;
3. ButtonLoading
Located at @/components/ui/button-loading
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:
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:
const { data, isLoading, error } = useQuery({
queryKey: ['key'],
queryFn: fetchData,
});
if (isLoading) {
return <LoadingSpinner />;
}
Pattern 3: Mutations with TanStack Query
Use isPending from useMutation:
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:
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:
<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 stateFollowButton.tsx- Inline loading stateAddCollaboratorModal.tsx- Mutation with loading stateNotificationsPage.tsx- Query with loading stateSearchPage.tsx- Multiple queries with loading states