# React Component

> Reactコンポーネントの設計と実装パターン。 関数コンポーネント、カスタムフック、状態管理、アクセシビリティ。 Reactコンポーネント作成、フロントエンド実装時に使用。

- Skill: `atomic-kanta-sasaki/react-component` (Agent Skill)
- Install (CLI): `npx skillmds@latest add atomic-kanta-sasaki/react-component`
- Raw SKILL.md: https://api.skillmd.com/api/skills/atomic-kanta-sasaki/react-component/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: atomic-kanta-sasaki (https://skillmd.com/u/atomic-kanta-sasaki)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/atomic-kanta-sasaki/react-component

---


# React Component Skill

## Overview
保守性の高いReactコンポーネント設計パターンを提供します。

## Component Structure

```
src/
├── components/
│   ├── ui/              # 汎用UIコンポーネント
│   │   ├── Button/
│   │   │   ├── Button.tsx
│   │   │   ├── Button.test.tsx
│   │   │   └── index.ts
│   │   └── Input/
│   ├── features/        # 機能別コンポーネント
│   │   └── auth/
│   │       ├── LoginForm.tsx
│   │       └── hooks/
│   └── layouts/         # レイアウト
├── hooks/               # 共通カスタムフック
└── types/               # 型定義
```

## Component Template

```tsx
import { forwardRef, type ComponentPropsWithoutRef } from 'react';
import { cn } from '@/lib/utils';

// Props定義
interface ButtonProps extends ComponentPropsWithoutRef<'button'> {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  isLoading?: boolean;
}

// コンポーネント実装
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  (
    {
      variant = 'primary',
      size = 'md',
      isLoading = false,
      disabled,
      className,
      children,
      ...props
    },
    ref
  ) => {
    return (
      <button
        ref={ref}
        disabled={disabled || isLoading}
        className={cn(
          // ベーススタイル
          'inline-flex items-center justify-center rounded-md font-medium',
          'transition-colors focus-visible:outline-none focus-visible:ring-2',
          // バリアント
          {
            'bg-blue-600 text-white hover:bg-blue-700': variant === 'primary',
            'bg-gray-200 text-gray-900 hover:bg-gray-300': variant === 'secondary',
            'bg-red-600 text-white hover:bg-red-700': variant === 'danger',
          },
          // サイズ
          {
            'h-8 px-3 text-sm': size === 'sm',
            'h-10 px-4 text-base': size === 'md',
            'h-12 px-6 text-lg': size === 'lg',
          },
          // 状態
          'disabled:opacity-50 disabled:cursor-not-allowed',
          className
        )}
        {...props}
      >
        {isLoading && <Spinner className="mr-2" />}
        {children}
      </button>
    );
  }
);

Button.displayName = 'Button';
```

## Custom Hook Pattern

```tsx
import { useState, useCallback } from 'react';

interface UseAsyncOptions<T> {
  onSuccess?: (data: T) => void;
  onError?: (error: Error) => void;
}

interface UseAsyncState<T> {
  data: T | null;
  error: Error | null;
  isLoading: boolean;
}

export function useAsync<T, Args extends unknown[]>(
  asyncFn: (...args: Args) => Promise<T>,
  options: UseAsyncOptions<T> = {}
) {
  const [state, setState] = useState<UseAsyncState<T>>({
    data: null,
    error: null,
    isLoading: false,
  });

  const execute = useCallback(
    async (...args: Args) => {
      setState(prev => ({ ...prev, isLoading: true, error: null }));
      
      try {
        const data = await asyncFn(...args);
        setState({ data, error: null, isLoading: false });
        options.onSuccess?.(data);
        return data;
      } catch (error) {
        const err = error instanceof Error ? error : new Error(String(error));
        setState({ data: null, error: err, isLoading: false });
        options.onError?.(err);
        throw err;
      }
    },
    [asyncFn, options]
  );

  return { ...state, execute };
}

// 使用例
function UserProfile({ userId }: { userId: string }) {
  const { data: user, isLoading, error, execute: fetchUser } = useAsync(
    () => api.getUser(userId),
    { onError: (e) => toast.error(e.message) }
  );

  useEffect(() => {
    fetchUser();
  }, [fetchUser]);

  if (isLoading) return <Skeleton />;
  if (error) return <ErrorMessage error={error} />;
  if (!user) return null;

  return <div>{user.name}</div>;
}
```

## Accessibility Checklist

- [ ] セマンティックHTML要素を使用
- [ ] キーボード操作可能（Tab, Enter, Escape）
- [ ] aria-label, aria-describedby 適切に設定
- [ ] フォーカス表示が明確
- [ ] 色だけに依存しない情報伝達
- [ ] 十分なカラーコントラスト

```tsx
// アクセシブルなモーダル例
<Dialog
  open={isOpen}
  onClose={onClose}
  aria-labelledby="dialog-title"
  aria-describedby="dialog-description"
>
  <DialogTitle id="dialog-title">確認</DialogTitle>
  <DialogDescription id="dialog-description">
    この操作は取り消せません。
  </DialogDescription>
  <DialogActions>
    <Button onClick={onClose}>キャンセル</Button>
    <Button onClick={onConfirm} variant="danger">削除</Button>
  </DialogActions>
</Dialog>
```

## Performance Patterns

```tsx
// 1. メモ化
const MemoizedComponent = memo(ExpensiveComponent);

// 2. 仮想化（大量リスト）
import { useVirtualizer } from '@tanstack/react-virtual';

// 3. 遅延ロード
const HeavyComponent = lazy(() => import('./HeavyComponent'));

// 4. 適切な状態配置
// ❌ 全体で共有
const [formData, setFormData] = useGlobalState();

// ✅ 必要な範囲で管理
function Form() {
  const [formData, setFormData] = useState();
  return <FormFields data={formData} onChange={setFormData} />;
}
```

