レビュー済み·難易度: 中級·更新: 2026-04-17

Redux Toolkit の createAsyncThunk で非同期処理を管理する

createAsyncThunk で API フェッチを定義し、pending / fulfilled / rejected の状態を extraReducers で管理する実装例。

nextjsstate-managementapiredux-toolkit

対応バージョン

nextjs 15react 19redux-toolkit 2

前提環境

Redux Toolkit の createSlice と Provider の基本を理解していること(redux-toolkit-slice を参照)

概要

createAsyncThunk を使うと、API フェッチのような非同期処理を pending / fulfilled / rejected の3状態で管理できる。 extraReducers で各状態に対応する reducer を定義し、ローディング・成功・エラーを slice の state に反映する。

インストール

npm install @reduxjs/toolkit react-redux

実装例

// src/store/postsSlice.ts
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";

type Post = { id: number; title: string; body: string };

type PostsState = {
  items: Post[];
  status: "idle" | "loading" | "succeeded" | "failed";
  error: string | null;
};

const initialState: PostsState = {
  items: [],
  status: "idle",
  error: null,
};

// 非同期処理を定義する
export const fetchPosts = createAsyncThunk(
  "posts/fetchAll",
  async (_, { rejectWithValue }) => {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=10");
    if (!res.ok) {
      return rejectWithValue(`HTTP error: ${res.status}`);
    }
    return (await res.json()) as Post[];
  }
);

export const postsSlice = createSlice({
  name: "posts",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchPosts.pending, (state) => {
        state.status = "loading";
        state.error = null;
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.status = "succeeded";
        state.items = action.payload;
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.status = "failed";
        state.error = action.payload as string;
      });
  },
});

export default postsSlice.reducer;
// src/store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import postsReducer from "./postsSlice";

export const store = configureStore({
  reducer: {
    posts: postsReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// src/components/PostList.tsx
"use client";

import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchPosts } from "@/store/postsSlice";
import type { AppDispatch, RootState } from "@/store";

export function PostList() {
  const dispatch = useDispatch<AppDispatch>();
  const { items, status, error } = useSelector((state: RootState) => state.posts);

  useEffect(() => {
    if (status === "idle") {
      dispatch(fetchPosts());
    }
  }, [dispatch, status]);

  if (status === "loading") return <p className="text-sm text-gray-400">読み込み中...</p>;
  if (status === "failed") return <p className="text-sm text-red-600">{error}</p>;

  return (
    <ul className="space-y-2">
      {items.map((post) => (
        <li key={post.id} className="rounded border px-4 py-2 text-sm">
          <p className="font-medium">{post.title}</p>
          <p className="text-gray-500">{post.body}</p>
        </li>
      ))}
    </ul>
  );
}

ポイント

  • createAsyncThunk の第1引数はアクション名("スライス名/アクション名" の形式が慣例)
  • rejectWithValue でエラーペイロードを型付きで返せる(action.payload で受け取れる)
  • extraReducersbuilder.addCasepending / fulfilled / rejected を個別に処理する
  • status === "idle" のときだけ dispatch することで、マウントのたびに再フェッチされるのを防ぐ
  • データフェッチのみが目的なら TanStack Query(useQuery)の方がシンプルに書ける

注意点

createAsyncThunk は fulfilled / rejected を自動生成する。エラーハンドリングは rejectWithValue で行うと型付きのエラーペイロードを渡せる。TanStack Query で代替できる場合はそちらが推奨。

関連サンプル