{
  "schema": "https://ai-atoms.com/schemas/skill-v1.json",
  "type": "skill",
  "id": "skill/react-ui-patterns",
  "version": "1.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.",
  "system_prompt_fragment": "# React UI Patterns\n\n## Core Principles\n\n1. **Never show stale UI** - Loading spinners only when actually loading\n2. **Always surface errors** - Users must know when something fails\n3. **Optimistic updates** - Make the UI feel instant\n4. **Progressive disclosure** - Show content as it becomes available\n5. **Graceful degradation** - Partial data is better than no data\n\n## Loading State Patterns\n\n### The Golden Rule\n\n**Show loading indicator ONLY when there's no data to display.**\n\n```typescript\n// CORRECT - Only show loading when no data exists\nconst { data, loading, error } = useGetItemsQuery();\n\nif (error) return <ErrorState error={error} onRetry={refetch} />;\nif (loading && !data) return <LoadingState />;\nif (!data?.items.length) return <EmptyState />;\n\nreturn <ItemList items={data.items} />;\n```\n\n```typescript\n// WRONG - Shows spinner even when we have cached data\nif (loading) return <LoadingState />; // Flashes on refetch!\n```\n\n### Loading State Decision Tree\n\n```\nIs there an error?\n  → Yes: Show error state with retry option\n  → No: Continue\n\nIs it loading AND we have no data?\n  → Yes: Show loading indicator (spinner/skeleton)\n  → No: Continue\n\nDo we have data?\n  → Yes, with items: Show the data\n  → Yes, but empty: Show empty state\n  → No: Show loading (fallback)\n```\n\n### Skeleton vs Spinner\n\n| Use Skeleton When | Use Spinner When |\n|-------------------|------------------|\n| Known content shape | Unknown content shape |\n| List/card layouts | Modal actions |\n| Initial page load | Button submissions |\n| Content placeholders | Inline operations |\n\n## Error Handling Patterns\n\n### The Error Handling Hierarchy\n\n```\n1. Inline error (field-level) → Form validation errors\n2. Toast notification → Recoverable errors, user can retry\n3. Error banner → Page-level errors, data still partially usable\n4. Full error screen → Unrecoverable, needs user action\n```\n\n### Always Show Errors\n\n**CRITICAL: Never swallow errors silently.**\n\n```typescript\n// CORRECT - Error always surfaced to user\nconst [createItem, { loading }] = useCreateItemMutation({\n  onCompleted: () => {\n    toast.success({ title: 'Item created' });\n  },\n  onError: (error) => {\n    console.error('createItem failed:', error);\n    toast.error({ title: 'Failed to create item' });\n  },\n});\n\n// WRONG - Error silently caught, user has no idea\nconst [createItem] = useCreateItemMutation({\n  onError: (error) => {\n    console.error(error); // User sees nothing!\n  },\n});\n```\n\n### Error State Component Pattern\n\n```typescript\ninterface ErrorStateProps {\n  error: Error;\n  onRetry?: () => void;\n  title?: string;\n}\n\nconst ErrorState = ({ error, onRetry, title }: ErrorStateProps) => (\n  <div className=\"error-state\">\n    <Icon name=\"exclamation-circle\" />\n    <h3>{title ?? 'Something went wrong'}</h3>\n    <p>{error.message}</p>\n    {onRetry && (\n      <Button onClick={onRetry}>Try Again</Button>\n    )}\n  </div>\n);\n```\n\n## Button State Patterns\n\n### Button Loading State\n\n```tsx\n<Button\n  onClick={handleSubmit}\n  isLoading={isSubmitting}\n  disabled={!isValid || isSubmitting}\n>\n  Submit\n</Button>\n```\n\n### Disable During Operations\n\n**CRITICAL: Always disable triggers during async operations.**\n\n```tsx\n// CORRECT - Button disabled while loading\n<Button\n  disabled={isSubmitting}\n  isLoading={isSubmitting}\n  onClick={handleSubmit}\n>\n  Submit\n</Button>\n\n// WRONG - User can tap multiple times\n<Button onClick={handleSubmit}>\n  {isSubmitting ? 'Submitting...' : 'Submit'}\n</Button>\n```\n\n## Empty States\n\n### Empty State Requirements\n\nEvery list/collection MUST have an empty state:\n\n```tsx\n// WRONG - No empty state\nreturn <FlatList data={items} />;\n\n// CORRECT - Explicit empty state\nreturn (\n  <FlatList\n    data={items}\n    ListEmptyComponent={<EmptyState />}\n  />\n);\n```\n\n### Contextual Empty States\n\n```tsx\n// Search with no results\n<EmptyState\n  icon=\"search\"\n  title=\"No results found\"\n  description=\"Try different search terms\"\n/>\n\n// List with no items yet\n<EmptyState\n  icon=\"plus-circle\"\n  title=\"No items yet\"\n  description=\"Create your first item\"\n  action={{ label: 'Create Item', onClick: handleCreate }}\n/>\n```\n\n## Form Submission Pattern\n\n```tsx\nconst MyForm = () => {\n  const [submit, { loading }] = useSubmitMutation({\n    onCompleted: handleSuccess,\n    onError: handleError,\n  });\n\n  const handleSubmit = async () => {\n    if (!isValid) {\n      toast.error({ title: 'Please fix errors' });\n      return;\n    }\n    await submit({ variables: { input: values } });\n  };\n\n  return (\n    <form>\n      <Input\n        value={values.name}\n        onChange={handleChange('name')}\n        error={touched.name ? errors.name : undefined}\n      />\n      <Button\n        type=\"submit\"\n        onClick={handleSubmit}\n        disabled={!isValid || loading}\n        isLoading={loading}\n      >\n        Submit\n      </Button>\n    </form>\n  );\n};\n```\n\n## Anti-Patterns\n\n### Loading States\n\n```typescript\n// WRONG - Spinner when data exists (causes flash)\nif (loading) return <Spinner />;\n\n// CORRECT - Only show loading without data\nif (loading && !data) return <Spinner />;\n```\n\n### Error Handling\n\n```typescript\n// WRONG - Error swallowed\ntry {\n  await mutation();\n} catch (e) {\n  console.log(e); // User has no idea!\n}\n\n// CORRECT - Error surfaced\nonError: (error) => {\n  console.error('operation failed:', error);\n  toast.error({ title: 'Operation failed' });\n}\n```\n\n### Button States\n\n```typescript\n// WRONG - Button not disabled during submission\n<Button onClick={submit}>Submit</Button>\n\n// CORRECT - Disabled and shows loading\n<Button onClick={submit} disabled={loading} isLoading={loading}>\n  Submit\n</Button>\n```\n\n## Checklist\n\nBefore completing any UI component:\n\n**UI States:**\n- [ ] Error state handled and shown to user\n- [ ] Loading state shown only when no data exists\n- [ ] Empty state provided for collections\n- [ ] Buttons disabled during async operations\n- [ ] Buttons show loading indicator when appropriate\n\n**Data & Mutations:**\n- [ ] Mutations have onError handler\n- [ ] All user actions have feedback (toast/visual)\n\n## Integration with Other Skills\n\n- **graphql-schema**: Use mutation patterns with proper error handling\n- **testing-patterns**: Test all UI states (loading, error, empty, success)\n- **formik-patterns**: Apply form submission patterns\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.",
  "applicable_domains": [
    "frontend"
  ],
  "category": "frontend",
  "invocation": [
    "/react-ui-patterns"
  ],
  "authored_by": "claudeskills.in community",
  "source_url": "https://claudeskills.in/skill/react-ui-patterns",
  "provenance": {
    "source": "claudeskills.in",
    "source_url": "https://claudeskills.in/skill/react-ui-patterns",
    "license": "unknown",
    "imported_at": "2026-09-03",
    "notes": "Aggregated by claudeskills.in from community GitHub lists."
  },
  "tags": [
    "claudeskills",
    "frontend"
  ],
  "lifecycle": "draft"
}