react-ui-patterns について
react-ui-patterns は、React インターフェースの読み込み、エラー、空状態、送信状態を一貫して扱うためのスキルです。非同期操作の結果を明確に伝え、古いデータや誤解を招く UI を避ける指針を示します。
主な機能
- 01データ状態に応じた読み込み表示
- 02エラーの明示
- 03楽観的更新の指針
- 04空状態のパターン
- 05非同期ボタンとフォームのパターン
- 06段階的なコンテンツ表示
活用シーン
- 01クエリの読み込み状態とエラー状態の設計
- 02変更結果を伝えるフォームの構築
- 03コンポーネントの空状態の不足確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill react-ui-patternsインストール後、Claude Code で/react-ui-patternsを入力して読み込まれたか確認します。
詳細
React UI パターン
基本原則
- 古い UI を決して表示しない - 実際に読み込み中の場合にのみローディングスピナーを表示する
- 常にエラーを表面化させる - 何かが失敗したときはユーザーに必ず知らせる
- 楽観的更新 - UI を瞬時に感じさせる
- 段階的な開示 - コンテンツが利用可能になったら表示する
- グレースフルデグラデーション - 部分的なデータは、データがないより優れている
ローディング状態のパターン
黄金律
表示するデータがない場合にのみ、ローディングインジケーターを表示します。
// CORRECT - Only show loading when no data exists
const { data, loading, error, refetch } = 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!
ローディング状態の決定木
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)
スケルトンとスピナーの使い分け
| スケルトンを使う場合 | スピナーを使う場合 |
|---|---|
| コンテンツの形状が既知 | コンテンツの形状が不明 |
| リスト/カードレイアウト | モーダルのアクション |
| 初回ページ読み込み | ボタンの送信 |
| コンテンツのプレースホルダー | インライン操作 |
エラーハンドリングのパターン
エラーハンドリングの階層
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
常にエラーを表示する
重要: エラーを黙って握りつぶさないでください。
// 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!
},
});
エラー状態コンポーネントのパターン
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>Unable to load this content. Try again if the problem persists.</p>
{onRetry && (
<Button onClick={onRetry}>Try Again</Button>
)}
</div>
);
ボタン状態のパターン
ボタンのローディング状態
<Button
onClick={handleSubmit}
isLoading={isSubmitting}
disabled={!isValid || isSubmitting}
>
Submit
</Button>
操作中の無効化
重要: 非同期操作中は常にトリガーを無効にしてください。
// 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>
空状態
空状態の要件
すべてのリスト/コレクションには空状態が必須です:
// WRONG - No empty state
return <FlatList data={items} />;
// CORRECT - Explicit empty state
return (
<FlatList
data={items}
ListEmptyComponent={<EmptyState />}
/>
);
状況に応じた空状態
// 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 }}
/>
フォーム送信のパターン
const MyForm = () => {
const [submit, { loading }] = useSubmitMutation({
onCompleted: handleSuccess,
onError: handleError,
});
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!isValid) {
toast.error({ title: 'Please fix errors' });
return;
}
await submit({ variables: { input: values } });
};
return (
<form onSubmit={handleSubmit}>
<Input
value={values.name}
onChange={handleChange('name')}
error={touched.name ? errors.name : undefined}
/>
<Button
type="submit"
disabled={!isValid || loading}
isLoading={loading}
>
Submit
</Button>
</form>
);
};
アンチパターン
ローディング状態
// WRONG - Spinner when data exists (causes flash)
if (loading) return <Spinner />;
// CORRECT - Only show loading without data
if (loading && !data) return <Spinner />;
エラーハンドリング
// 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' });
}
ボタンの状態
// 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>
チェックリスト
UI コンポーネントを完成させる前に:
UI 状態:
- エラー状態が処理され、ユーザーに表示されている
- データが存在しない場合にのみローディング状態が表示される
- コレクションに空状態が用意されている
- 非同期操作中はボタンが無効化されている
- ボタンが適切な場合にローディングインジケーターを表示する
データとミューテーション:
- ミューテーションに onError ハンドラーがある
- すべてのユーザーアクションにフィードバック (トースト/視覚的) がある
他のスキルとの統合
- graphql-schema: 適切なエラーハンドリングを伴うミューテーションパターンを使用する
- testing-patterns: すべての UI 状態 (ローディング、エラー、空、成功) をテストする
- formik-patterns: フォーム送信パターンを適用する
使用する場合
このスキルは、概要で説明されているワークフローまたはアクションを実行する場合に適用できます。
制限事項
- このスキルは、タスクが上記で説明されている範囲に明確に一致する場合にのみ使用してください。
- 出力を、環境固有の検証、テスト、専門家によるレビューの代替として扱わないでください。
- 必要な入力、権限、安全境界、または成功基準が欠けている場合は、停止して明確化を求めてください。
コメント
Markdown 対応