Reorganize: Move all skills to skills/ folder
- Created skills/ directory - Moved 272 skills to skills/ subfolder - Kept agents/ at root level - Kept installation scripts and docs at root level Repository structure: - skills/ - All 272 skills from skills.sh - agents/ - Agent definitions - *.sh, *.ps1 - Installation scripts - README.md, etc. - Documentation Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,289 +0,0 @@
|
||||
---
|
||||
name: react-ui-patterns
|
||||
description: Modern React UI patterns for loading states, error handling, and data fetching. Use when building UI components, handling async data, or managing UI states.
|
||||
---
|
||||
|
||||
# React UI Patterns
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Never show stale UI** - Loading spinners only when actually loading
|
||||
2. **Always surface errors** - Users must know when something fails
|
||||
3. **Optimistic updates** - Make the UI feel instant
|
||||
4. **Progressive disclosure** - Show content as it becomes available
|
||||
5. **Graceful degradation** - Partial data is better than no data
|
||||
|
||||
## Loading State Patterns
|
||||
|
||||
### The Golden Rule
|
||||
|
||||
**Show loading indicator ONLY when there's no data to display.**
|
||||
|
||||
```typescript
|
||||
// CORRECT - Only show loading when no data exists
|
||||
const { data, loading, error } = useGetItemsQuery();
|
||||
|
||||
if (error) return <ErrorState error={error} onRetry={refetch} />;
|
||||
if (loading && !data) return <LoadingState />;
|
||||
if (!data?.items.length) return <EmptyState />;
|
||||
|
||||
return <ItemList items={data.items} />;
|
||||
```
|
||||
|
||||
```typescript
|
||||
// WRONG - Shows spinner even when we have cached data
|
||||
if (loading) return <LoadingState />; // Flashes on refetch!
|
||||
```
|
||||
|
||||
### Loading State Decision Tree
|
||||
|
||||
```
|
||||
Is there an error?
|
||||
→ Yes: Show error state with retry option
|
||||
→ No: Continue
|
||||
|
||||
Is it loading AND we have no data?
|
||||
→ Yes: Show loading indicator (spinner/skeleton)
|
||||
→ No: Continue
|
||||
|
||||
Do we have data?
|
||||
→ Yes, with items: Show the data
|
||||
→ Yes, but empty: Show empty state
|
||||
→ No: Show loading (fallback)
|
||||
```
|
||||
|
||||
### Skeleton vs Spinner
|
||||
|
||||
| Use Skeleton When | Use Spinner When |
|
||||
|-------------------|------------------|
|
||||
| Known content shape | Unknown content shape |
|
||||
| List/card layouts | Modal actions |
|
||||
| Initial page load | Button submissions |
|
||||
| Content placeholders | Inline operations |
|
||||
|
||||
## Error Handling Patterns
|
||||
|
||||
### The Error Handling Hierarchy
|
||||
|
||||
```
|
||||
1. Inline error (field-level) → Form validation errors
|
||||
2. Toast notification → Recoverable errors, user can retry
|
||||
3. Error banner → Page-level errors, data still partially usable
|
||||
4. Full error screen → Unrecoverable, needs user action
|
||||
```
|
||||
|
||||
### Always Show Errors
|
||||
|
||||
**CRITICAL: Never swallow errors silently.**
|
||||
|
||||
```typescript
|
||||
// CORRECT - Error always surfaced to user
|
||||
const [createItem, { loading }] = useCreateItemMutation({
|
||||
onCompleted: () => {
|
||||
toast.success({ title: 'Item created' });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('createItem failed:', error);
|
||||
toast.error({ title: 'Failed to create item' });
|
||||
},
|
||||
});
|
||||
|
||||
// WRONG - Error silently caught, user has no idea
|
||||
const [createItem] = useCreateItemMutation({
|
||||
onError: (error) => {
|
||||
console.error(error); // User sees nothing!
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Error State Component Pattern
|
||||
|
||||
```typescript
|
||||
interface ErrorStateProps {
|
||||
error: Error;
|
||||
onRetry?: () => void;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
const ErrorState = ({ error, onRetry, title }: ErrorStateProps) => (
|
||||
<div className="error-state">
|
||||
<Icon name="exclamation-circle" />
|
||||
<h3>{title ?? 'Something went wrong'}</h3>
|
||||
<p>{error.message}</p>
|
||||
{onRetry && (
|
||||
<Button onClick={onRetry}>Try Again</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
## Button State Patterns
|
||||
|
||||
### Button Loading State
|
||||
|
||||
```tsx
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
isLoading={isSubmitting}
|
||||
disabled={!isValid || isSubmitting}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Disable During Operations
|
||||
|
||||
**CRITICAL: Always disable triggers during async operations.**
|
||||
|
||||
```tsx
|
||||
// CORRECT - Button disabled while loading
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
isLoading={isSubmitting}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
// WRONG - User can tap multiple times
|
||||
<Button onClick={handleSubmit}>
|
||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Empty States
|
||||
|
||||
### Empty State Requirements
|
||||
|
||||
Every list/collection MUST have an empty state:
|
||||
|
||||
```tsx
|
||||
// WRONG - No empty state
|
||||
return <FlatList data={items} />;
|
||||
|
||||
// CORRECT - Explicit empty state
|
||||
return (
|
||||
<FlatList
|
||||
data={items}
|
||||
ListEmptyComponent={<EmptyState />}
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Contextual Empty States
|
||||
|
||||
```tsx
|
||||
// Search with no results
|
||||
<EmptyState
|
||||
icon="search"
|
||||
title="No results found"
|
||||
description="Try different search terms"
|
||||
/>
|
||||
|
||||
// List with no items yet
|
||||
<EmptyState
|
||||
icon="plus-circle"
|
||||
title="No items yet"
|
||||
description="Create your first item"
|
||||
action={{ label: 'Create Item', onClick: handleCreate }}
|
||||
/>
|
||||
```
|
||||
|
||||
## Form Submission Pattern
|
||||
|
||||
```tsx
|
||||
const MyForm = () => {
|
||||
const [submit, { loading }] = useSubmitMutation({
|
||||
onCompleted: handleSuccess,
|
||||
onError: handleError,
|
||||
});
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!isValid) {
|
||||
toast.error({ title: 'Please fix errors' });
|
||||
return;
|
||||
}
|
||||
await submit({ variables: { input: values } });
|
||||
};
|
||||
|
||||
return (
|
||||
<form>
|
||||
<Input
|
||||
value={values.name}
|
||||
onChange={handleChange('name')}
|
||||
error={touched.name ? errors.name : undefined}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={handleSubmit}
|
||||
disabled={!isValid || loading}
|
||||
isLoading={loading}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
### Loading States
|
||||
|
||||
```typescript
|
||||
// WRONG - Spinner when data exists (causes flash)
|
||||
if (loading) return <Spinner />;
|
||||
|
||||
// CORRECT - Only show loading without data
|
||||
if (loading && !data) return <Spinner />;
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```typescript
|
||||
// WRONG - Error swallowed
|
||||
try {
|
||||
await mutation();
|
||||
} catch (e) {
|
||||
console.log(e); // User has no idea!
|
||||
}
|
||||
|
||||
// CORRECT - Error surfaced
|
||||
onError: (error) => {
|
||||
console.error('operation failed:', error);
|
||||
toast.error({ title: 'Operation failed' });
|
||||
}
|
||||
```
|
||||
|
||||
### Button States
|
||||
|
||||
```typescript
|
||||
// WRONG - Button not disabled during submission
|
||||
<Button onClick={submit}>Submit</Button>
|
||||
|
||||
// CORRECT - Disabled and shows loading
|
||||
<Button onClick={submit} disabled={loading} isLoading={loading}>
|
||||
Submit
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
Before completing any UI component:
|
||||
|
||||
**UI States:**
|
||||
- [ ] Error state handled and shown to user
|
||||
- [ ] Loading state shown only when no data exists
|
||||
- [ ] Empty state provided for collections
|
||||
- [ ] Buttons disabled during async operations
|
||||
- [ ] Buttons show loading indicator when appropriate
|
||||
|
||||
**Data & Mutations:**
|
||||
- [ ] Mutations have onError handler
|
||||
- [ ] All user actions have feedback (toast/visual)
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
- **graphql-schema**: Use mutation patterns with proper error handling
|
||||
- **testing-patterns**: Test all UI states (loading, error, empty, success)
|
||||
- **formik-patterns**: Apply form submission patterns
|
||||
Reference in New Issue
Block a user