react-ui-patterns

现代React UI模式:加载状态、错误处理与数据获取。适用于构建UI组件、处理异步数据或管理UI状态。

查看详情
name:react-ui-patternsdescription: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

  • 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 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 errors
  • Toast notification → Recoverable errors, user can retry

  • Error banner → Page-level errors, data still partially usable

  • 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

    1. react-ui-patterns - Agent Skills