react-ui-patterns
sickn33/agentic-awesome-skills
Modern React UI patterns for loading states, error handling, and data fetching.
What is react-ui-patterns?
Provides best practices for building React components with proper handling of async data, loading indicators, error states, and empty states. Use when building UI components, managing async operations, or ensuring users always see appropriate feedback for their actions.
- Show loading indicators only when no data exists to avoid UI flashing
- Surface all errors to users with retry options and contextual messaging
- Disable buttons during async operations to prevent duplicate submissions
- Provide empty states for all collections and lists
- Apply skeleton loaders vs spinners based on content shape and context
- Implement optimistic updates and progressive content disclosure
How to install react-ui-patterns
npx skills add https://github.com/sickn33/agentic-awesome-skills --skill react-ui-patternsHow to use react-ui-patterns
- 1.Review the loading state decision tree to determine when to show spinners vs skeletons
- 2.Implement error handling with onError callbacks that surface messages to users via toast or error banners
- 3.Add empty state components to all list/collection renders
- 4.Disable and show loading state on buttons during async operations
- 5.Check your component against the provided checklist before completion
Use cases
- Building form submission flows with proper loading and error feedback
- Creating list components with empty, loading, and error states
- Implementing button states that prevent accidental double-submission
- Handling GraphQL mutations with appropriate user-facing error messages
- Designing search results pages with no-results empty states
- React component developers
- Frontend engineers building data-driven UIs
- Teams using GraphQL or async data fetching
- Developers implementing form submissions and mutations
react-ui-patterns FAQ
Show a skeleton when you know the content shape (lists, cards, initial page load) and a spinner when the shape is unknown (modal actions, button submissions, inline operations).
Showing a spinner when cached data exists causes UI flashing on refetch, degrading user experience. Only show loading when there's genuinely no data to display.
Inline field errors for validation, toast notifications for recoverable errors, error banners for page-level issues, and full error screens only for unrecoverable failures.
Disable the submit button and show a loading indicator while the mutation is in progress using the isLoading and disabled props.
Always implement an onError handler that surfaces the error to the user via toast notification, error banner, or inline message—never silently swallow errors.
Full instructions (SKILL.md)
Source of truth, from sickn33/agentic-awesome-skills.
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." risk: critical source: community date_added: "2026-02-27"
React UI Patterns
Core Principles
- Never show stale UI - Loading spinners only when actually loading
- Always surface errors - Users must know when something fails
- Optimistic updates - Make the UI feel instant
- Progressive disclosure - Show content as it becomes available
- 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.
// 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} />;
// 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.
// 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
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
<Button
onClick={handleSubmit}
isLoading={isSubmitting}
disabled={!isValid || isSubmitting}
>
Submit
</Button>
Disable During Operations
CRITICAL: Always disable triggers during async operations.
// 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:
// WRONG - No empty state
return <FlatList data={items} />;
// CORRECT - Explicit empty state
return (
<FlatList
data={items}
ListEmptyComponent={<EmptyState />}
/>
);
Contextual Empty States
// 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
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
// WRONG - Spinner when data exists (causes flash)
if (loading) return <Spinner />;
// CORRECT - Only show loading without data
if (loading && !data) return <Spinner />;
Error Handling
// 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
// 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
When to Use
This skill is applicable to execute the workflow or actions described in the overview.
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
Related skills
More from sickn33/agentic-awesome-skills and the wider catalog.

scroll-experience
Build immersive scroll-driven experiences with parallax, animations, and cinematic storytelling.

security-audit
Orchestrate comprehensive security audits covering reconnaissance, vulnerability scanning, web app testing, API security, and penetration testing.

security-auditor
Expert security auditor for DevSecOps, vulnerability assessment, and compliance frameworks.

security-review
Agent skill from sickn33/agentic-awesome-skills.

senior-architect
Complete toolkit for senior architects with automated analysis, diagram generation, and dependency management.

senior-fullstack
Complete toolkit for senior fullstack development with modern tools and best practices.