jpskill.com
🛠️ 開発・MCP コミュニティ

react-async-patterns

Async/await correctness in React with Zustand. Use when debugging race conditions, missing awaits, floating promises, or async timing issues. Works for both React web and React Native.

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o react-async-patterns.zip https://jpskill.com/download/17849.zip && unzip -o react-async-patterns.zip && rm react-async-patterns.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/17849.zip -OutFile "$d\react-async-patterns.zip"; Expand-Archive "$d\react-async-patterns.zip" -DestinationPath $d -Force; ri "$d\react-async-patterns.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して react-async-patterns.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → react-async-patterns フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

React Async Patterns

問題提起

React における非同期処理のバグは、開発環境では動作するものの、負荷がかかったり、エッジケースで失敗したりするため、見つけにくいものです。最も一般的な問題は、async 関数での await の欠落、状態更新間の競合状態、操作が順番に完了することを前提としていることです。


パターン: Floating Promise の検出

問題: await なしで async 関数を呼び出すと、バックグラウンドで実行されます。後続のコードがその完了に依存する場合、競合状態が発生します。

// 以前 (バグあり) - saveData は async だが await されていない
saveData(item);              // Fire and forget ❌
await processData(item);     // save が完了する前に実行される

// 以後 (修正済み)
await saveData(item);        // 状態更新を待つ ✅
await processData(item);     // 正しい順序で実行される

なぜ見つけにくいのか: 両方の関数がシグネチャに async を持っている可能性がありますが、一方だけが await されています。コードは一見「正しく」見えます。

検出:

# Floating Promise の可能性を検出 - await のない async 呼び出し
grep -rn "^\s*[a-zA-Z]*\s*(" --include="*.ts" --include="*.tsx" | \
  grep -v "await\|return\|const\|let\|if\|else\|=>"

予防:

  1. ESLint rule @typescript-eslint/no-floating-promises - lint 時にこれを検出します
  2. コードレビューのトリガー: awaitreturn、または代入なしに、async である可能性のある関数を呼び出す行

パターン: 事後条件の検証

問題: 検証せずに async 呼び出しが成功したと仮定すること。呼び出しが途中で返されたり、サイレントに例外をスローしたり、状態の更新に失敗したりする可能性があります。

// 以前 (バグあり) - ロードが成功したと仮定
await loadData(id);
// 次のステップを盲目的に進める...

// 以後 (防御的)
await loadData(id);
const loaded = useStore.getState().data;
if (Object.keys(loaded).length === 0) {
  throw new Error(
    `Failed to load data for ${id} - cannot proceed`
  );
}

原則: すべての async 呼び出しは、そうでないと証明されるまで、失敗する可能性があるものとして扱います。

検証のタイミング:

  • 後続の操作が依存するデータをロードした後
  • 続行する前に完了する必要がある状態更新の後
  • 不可逆的な操作 (送信、削除) の前

パターンテンプレート:

await someAsyncOperation();
const result = getRelevantState();
if (!isValid(result)) {
  throw new Error(`[${functionName}] Post-condition failed: ${diagnosticContext}`);
}

パターン: Async 関数の識別

問題: すべての async 関数が async に見えるわけではありません。Zustand アクション、コールバック、および Promise を返す関数には、明らかな async キーワードがない場合があります。

隠れた async パターン:

// 明らかな async
async function fetchData() { ... }

// あまり明らかではない - Promise を返す
function fetchData(): Promise<Data> { ... }

// 隠れている - 実際には async である Zustand アクション
const useStore = create((set, get) => ({
  // これは同期的に見えるが、内部で async を呼び出す
  enableFeature: (id: string) => {
    someAsyncSetup().then(() => {  // ← 隠れた async!
      set({ features: [...get().features, id] });
    });
  },
}));

// 適切な async Zustand アクション
const useStore = create((set, get) => ({
  enableFeature: async (id: string) => {
    await someAsyncSetup();
    set({ features: [...get().features, id] });
  },
}));

検出: 関数のシグネチャと実装を確認します。

# Promise を返す関数を検索
grep -rn "): Promise<" --include="*.ts" --include="*.tsx"

# await が必要な可能性がある .then() チェーンを検索
grep -rn "\.then(" --include="*.ts" --include="*.tsx"

パターン: 逐次的な Async vs 並列的な Async

問題: 並列化できる場合に async 操作を逐次的に実行する (遅い)、または順序付けが必要な場合に並列的に実行する (競合状態)。

// 逐次的 - 順序が重要な場合に正しい
await stepOne();
await stepTwo();
await stepThree();

// 並列的 - 操作が独立している場合に正しい
const [user, settings, history] = await Promise.all([
  fetchUser(id),
  fetchSettings(id),
  fetchHistory(id),
]);

// 間違い - 順序が重要な場合に並列的
await Promise.all([
  stepOne(),   // これらには依存関係がある!
  stepTwo(),
]);

意思決定フレームワーク:

操作は状態を共有するか? 順番に実行する必要があるか? パターン
いいえ いいえ Promise.all()
はい はい 逐次的な await
はい いいえ 安全のため通常は逐次的

パターン: useEffect での Async

問題: useEffect コールバックは直接 async にできません。クリーンアップと競合状態に関する一般的な間違い。

// 間違い - useEffect は async にできない
useEffect(async () => {
  const data = await fetchData();
  setData(data);
}, []);

// 正しい - 内部の async 関数
useEffect(() => {
  async function load() {
    const data = await fetchData();
    setData(data);
  }
  load();
}, []);

// より良い - 競合状態のためのクリーンアップ付き
useEffect(() => {
  let cancelled = false;

  async function load() {
    const data = await fetchData();
    if (!cancelled) {
      setData(data);
    }
  }
  load();

  return () => {
    cancelled = true;
  };
}, [dependency]);

// 最高 - fetch に AbortController を使用
useEffect(() => {
  const controller = new AbortController();

  async function load() {
    try {
      const response = await fetch(url, { signal: controller.signal });
      const data = await response.json();
      setData(data);
    } catch (error) {
      if (error.name !== 'AbortError') {
        setError(error);
      }
    }
  }
  load();

  return () => controller.abort();
}, [url]);

パターン: React Query / TanStack Query

問題: 手動での async 状態管理はエラーが発生しやすいです。ライブラリを使用してください。


import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// データのフェッチ
function UserProfile({ userId }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });

  if (isLoading) return <Spinner />;
  if (error) return <Error erro
(原文がここで切り詰められています)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

React Async Patterns

Problem Statement

Async bugs in React are insidious because they often work in development but fail under load or in edge cases. The most common issues: missing await on async functions, race conditions between state updates, and assuming operations complete in order.


Pattern: Floating Promise Detection

Problem: Calling an async function without await causes it to run in the background. If subsequent code depends on its completion, you get a race condition.

// Before (buggy) - saveData is async but not awaited
saveData(item);              // Fire and forget ❌
await processData(item);     // Runs before save completes

// After (fixed)
await saveData(item);        // Wait for state update ✅
await processData(item);     // Now runs in correct order

Why it's subtle: Both functions might have async in their signature, but only one was awaited. The code "looks right" at a glance.

Detection:

# Find potential floating promises - async calls without await
grep -rn "^\s*[a-zA-Z]*\s*(" --include="*.ts" --include="*.tsx" | \
  grep -v "await\|return\|const\|let\|if\|else\|=>"

Prevention:

  1. ESLint rule @typescript-eslint/no-floating-promises - catches this at lint time
  2. Code review trigger: Any line calling a function that might be async without await, return, or assignment

Pattern: Post-Condition Validation

Problem: Assuming an async call succeeded without verifying. The call might return early, throw silently, or fail to update state.

// Before (buggy) - assumed load worked
await loadData(id);
// Proceeded blindly with next steps...

// After (defensive)
await loadData(id);
const loaded = useStore.getState().data;
if (Object.keys(loaded).length === 0) {
  throw new Error(
    `Failed to load data for ${id} - cannot proceed`
  );
}

Principle: Treat every async call as potentially failed until proven otherwise.

When to validate:

  • After loading data that subsequent operations depend on
  • After state updates that must complete before continuing
  • Before irreversible operations (submissions, deletions)

Pattern template:

await someAsyncOperation();
const result = getRelevantState();
if (!isValid(result)) {
  throw new Error(`[${functionName}] Post-condition failed: ${diagnosticContext}`);
}

Pattern: Async Function Identification

Problem: Not all async functions look async. Zustand actions, callbacks, and promise-returning functions may not have obvious async keywords.

Hidden async patterns:

// Obvious async
async function fetchData() { ... }

// Less obvious - returns Promise
function fetchData(): Promise<Data> { ... }

// Hidden - Zustand action that's actually async
const useStore = create((set, get) => ({
  // This looks sync but calls async internally
  enableFeature: (id: string) => {
    someAsyncSetup().then(() => {  // ← Hidden async!
      set({ features: [...get().features, id] });
    });
  },
}));

// Proper async Zustand action
const useStore = create((set, get) => ({
  enableFeature: async (id: string) => {
    await someAsyncSetup();
    set({ features: [...get().features, id] });
  },
}));

Detection: Check function signatures and implementations:

# Find functions returning Promise
grep -rn "): Promise<" --include="*.ts" --include="*.tsx"

# Find .then() chains that might need await
grep -rn "\.then(" --include="*.ts" --include="*.tsx"

Pattern: Sequential vs Parallel Async

Problem: Running async operations sequentially when they could be parallel (slow), or parallel when they must be sequential (race condition).

// Sequential - correct when order matters
await stepOne();
await stepTwo();
await stepThree();

// Parallel - correct when operations are independent
const [user, settings, history] = await Promise.all([
  fetchUser(id),
  fetchSettings(id),
  fetchHistory(id),
]);

// WRONG - parallel when order matters
await Promise.all([
  stepOne(),   // These have dependencies!
  stepTwo(),
]);

Decision framework:

Operations share state? Must run in order? Pattern
No No Promise.all()
Yes Yes Sequential await
Yes No Usually sequential to be safe

Pattern: Async in useEffect

Problem: useEffect callbacks can't be async directly. Common mistakes with cleanup and race conditions.

// WRONG - useEffect can't be async
useEffect(async () => {
  const data = await fetchData();
  setData(data);
}, []);

// CORRECT - async function inside
useEffect(() => {
  async function load() {
    const data = await fetchData();
    setData(data);
  }
  load();
}, []);

// BETTER - with cleanup for race conditions
useEffect(() => {
  let cancelled = false;

  async function load() {
    const data = await fetchData();
    if (!cancelled) {
      setData(data);
    }
  }
  load();

  return () => {
    cancelled = true;
  };
}, [dependency]);

// BEST - use AbortController for fetch
useEffect(() => {
  const controller = new AbortController();

  async function load() {
    try {
      const response = await fetch(url, { signal: controller.signal });
      const data = await response.json();
      setData(data);
    } catch (error) {
      if (error.name !== 'AbortError') {
        setError(error);
      }
    }
  }
  load();

  return () => controller.abort();
}, [url]);

Pattern: React Query / TanStack Query

Problem: Manual async state management is error-prone. Use a library.

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Fetching data
function UserProfile({ userId }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });

  if (isLoading) return <Spinner />;
  if (error) return <Error error={error} />;
  return <Profile user={data} />;
}

// Mutations with cache invalidation
function UpdateUser() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: updateUser,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['user'] });
    },
  });

  return (
    <button onClick={() => mutation.mutate(userData)}>
      Save
    </button>
  );
}

ESLint Configuration

Add these rules to catch async issues at lint time:

{
  "rules": {
    "@typescript-eslint/no-floating-promises": "error",
    "@typescript-eslint/require-await": "warn",
    "@typescript-eslint/await-thenable": "error",
    "@typescript-eslint/no-misused-promises": "error"
  }
}

Required: @typescript-eslint/eslint-plugin and proper TypeScript configuration.


Code Review Checklist

When reviewing async code, check:

  • [ ] Every async function call is either awaited, returned, or explicitly fire-and-forget with comment
  • [ ] Operations that depend on each other are sequenced with await
  • [ ] Post-conditions validated after critical async operations
  • [ ] useEffect with async uses the inner function pattern
  • [ ] Race conditions considered when component could unmount during async
  • [ ] Error handling exists for async failures
  • [ ] AbortController used for fetch calls that should be cancellable

Quick Debugging

When async timing issues occur:

// Add timestamps to trace execution order
console.log(`[${Date.now()}] Starting step 1`);
await stepOne();
console.log(`[${Date.now()}] Finished step 1`);
console.log(`[${Date.now()}] Starting step 2`);
await stepTwo();
console.log(`[${Date.now()}] Finished step 2`);

Look for:

  • Operations finishing out of expected order
  • Operations starting before previous ones complete
  • Suspiciously fast "completions" (might not have awaited)