ChatGPT Claude Codex CLI Cohere Cursor DeepSeek Gemini GitHub Copilot GLM Grok Kimi Llama MiniMax Mistral OpenAI opencode Skill

react-ui-patterns

Modern React UI patterns for loading states, error handling, and data fetching. Use when building UI components, handling async data, or managing UI states.

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

Full trust report

Download sickn33-agentic-awesome-skills-skills_react-ui-patterns-1f67c44.zip · 2 KB
Part of sickn33/agentic-awesome-skills — 427 skills
This skill couldn't be refreshed from GitHub on the last check — you're seeing the last imported snapshot.

Install

skills CLI npx skills add https://github.com/sickn33/agentic-awesome-skills/tree/main/skills/react-ui-patterns
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install sickn33-agentic-awesome-skills@llmmart
Git git clone https://github.com/sickn33/agentic-awesome-skills.git

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

Skill manifest

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.

// 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.
Files (agentic-awesome-skills)
  • SKILL.md 6.8 KB
    ---
    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
    
    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
    
    ## 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.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related