React Ui Patterns

by davila78da17d671b6fNo license32K starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated today

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

Instructions onlySoftware Development
AI-generated overview

Guidance for React UI patterns covering loading states, error handling, empty states, and form submission.

What it does
This skill provides reference guidance and code examples for building React UI components with consistent loading, error, empty, and button states. It covers decision trees for when to show spinners versus skeletons, an error-handling hierarchy, and patterns for surfacing errors and disabling buttons during async operations. It produces instructional content and example snippets rather than runnable code.
When to use it
Use it when building UI components, handling async data fetching, or managing UI states in a React application. It is also relevant when reviewing components for missing error, loading, or empty states.
Requirements
No scripts or tooling are required; it is instructions only. The examples reference React, TypeScript, and GraphQL-style hooks, but no packages or credentials are needed to read the guidance.

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 existsconst { 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 dataif (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 WhenUse Spinner When
Known content shapeUnknown content shape
List/card layoutsModal actions
Initial page loadButton submissions
Content placeholdersInline operations

Error Handling Patterns

The Error Handling Hierarchy

1. Inline error (field-level) → Form validation errors2. Toast notification → Recoverable errors, user can retry3. Error banner → Page-level errors, data still partially usable4. Full error screen → Unrecoverable, needs user action

Always Show Errors

CRITICAL: Never swallow errors silently.

typescript
// CORRECT - Error always surfaced to userconst [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 ideaconst [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 statereturn <FlatList data={items} />;
// CORRECT - Explicit empty statereturn (  <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 dataif (loading && !data) return <Spinner />;

Error Handling

typescript
// WRONG - Error swallowedtry {  await mutation();} catch (e) {  console.log(e); // User has no idea!}
// CORRECT - Error surfacedonError: (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

Source and attribution

Source:davila7/claude-code-templatesincli-tool/components/skills/development/react-ui-patternsat commit8da17d6

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal