Belajar Redux - Async dengan createAsyncThunk
Episode 6 of 23

Belajar Redux - Async dengan createAsyncThunk

Episode ini mengajarkan createAsyncThunk untuk mengambil data API dan menyimpan hasilnya di reducer. Kalian memahami lifecycle action pending, fulfilled, dan rejected, mengelola status loading, succeeded, dan failed, menangani error message, serta membatalkan request saat komponen unmount.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Semua yang kita kerjakan sejauh ini masih sinkron. Aplikasi sungguhan selalu bertemu dengan data dari server: fetching user, posting form, upload file — semuanya async. Redux Toolkit menyediakan createAsyncThunk untuk menangani ini dengan rapi.

Episode 6 membahas cara membuat async thunk, tiga lifecycle action yang digenerate otomatis, pola status loading, succeeded, dan failed, penanganan error, serta mekanisme abort ketika komponen sudah tidak lagi aktif.

Mengenal createAsyncThunk

Membuat Async Thunk Pertama

createAsyncThunk(typePrefix, payloadCreator) menerima dua argumen: prefix nama action dan fungsi async yang mengembalikan hasil.

JSThunk untuk mengambil posts
import { createAsyncThunk } from "@reduxjs/toolkit"
 
export const fetchPosts = createAsyncThunk("posts/fetchPosts", async () => {
  const res = await fetch("/api/posts")
  const data = await res.json()
  return data
})

"posts/fetchPosts" adalah prefix; dari prefix ini RTK menggenerate tiga action type: posts/fetchPosts/pending, posts/fetchPosts/fulfilled, dan posts/fetchPosts/rejected. createAsyncThunk menciptakan thunk yang bisa di-dispatch seperti action biasa.

Memakai Thunk di Komponen

JSDispatch thunk di komponen
const dispatch = useDispatch()
 
useEffect(() => {
  dispatch(fetchPosts())
}, [dispatch])

dispatch(fetchPosts()) menjalankan payloadCreator, lalu mengirim action pending, fulfilled, atau rejected sesuai hasilnya — tanpa perlu menangani promise secara manual di komponen.

Lifecycle Action

Tiga Action yang Digenerate

RTK mengirim tiga action secara berurutan:

Urutan lifecycle thunk
dispatch(thunk) -> pending -> (success) -> fulfilled
                              -> (error)  -> rejected
  • pending: dikirim saat thunk mulai dijalankan — ideal untuk menyalakan indikator loading.
  • fulfilled: dikirim saat payloadCreator berhasil, membawa hasil sebagai action.payload.
  • rejected: dikirim saat terjadi error, membawa action.error.

Perhatikan bahwa createAsyncThunk hanya melempar error jika payloadCreator melempar exception atau promise-nya reject. Error dari response HTTP seperti status 500 tidak otomatis menolak — respon tetap resolved selama fetch berhasil.

Mengelola Status dan Error

Pola Status per Slice

Pola standar yang direkomendasikan adalah menyimpan status dan error di state slice, lalu menanganinya di extraReducers:

JSSlice dengan status async
import { createSlice } from "@reduxjs/toolkit"
import { fetchPosts } from "./fetchPosts"
 
const postsSlice = createSlice({
  name: "posts",
  initialState: { items: [], status: "idle", error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchPosts.pending, (state) => {
        state.status = "loading"
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.status = "succeeded"
        state.items = action.payload
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.status = "failed"
        state.error = action.error.message
      })
  },
})

fetchPosts.pending, .fulfilled, dan .rejected adalah action creator yang digenerate — bisa langsung dipakai di builder.addCase. state.error = action.error.message menyimpan pesan error untuk ditampilkan.

Menampilkan Status di UI

JSRender berdasarkan status
const { items, status, error } = useSelector((state) => state.posts)
 
if (status === "loading") return <p>Memuat...</p>
if (status === "failed") return <p>Error: {error}</p>
return <ul>{items.map((p) => <li key={p.id}>{p.title}</li>)}</ul>

Pola status === "loading" kemudian "failed" lalu render data adalah siklus UI async yang paling umum. Status tetap tersimpan di store sehingga bisa dibagikan antar komponen.

Handling Error dan Abort

Meneruskan Error dari RejectWithValue

Untuk error dari sisi bisnis — misalnya validasi 400 dari server — gunakan rejectWithValue agar pesan custom sampai ke reducer:

JSReject dengan pesan custom
export const fetchPosts = createAsyncThunk(
  "posts/fetchPosts",
  async (_, { rejectWithValue }) => {
    const res = await fetch("/api/posts")
    if (!res.ok) {
      return rejectWithValue("Gagal memuat data")
    }
    return res.json()
  },
)

rejectWithValue mengirim action rejected dengan payload pesan yang bisa dibaca sebagai action.payload di reducer. createAsyncThunk menyediakan argumen kedua berupa objek dengan rejectWithValue, getState, dan dispatch.

Abort Request saat Komponen Unmount

Thunk menerima signal dari AbortController bawaan. fetch mendukung signal ini, sehingga request bisa dibatalkan:

JSMembatalkan request yang tidak lagi dibutuhkan
export const fetchPosts = createAsyncThunk(
  "posts/fetchPosts",
  async (_, { signal }) => {
    const res = await fetch("/api/posts", { signal })
    return res.json()
  },
)

Saat komponen unmount, React Redux membatalkan dispatch yang belum selesai lewat mekanisme internal. Menyatukan signal ke fetch memastikan request HTTP benar-benar dibatalkan dan tidak membuang sumber daya.

Penutup

Episode 6 membuka dunia async di Redux: createAsyncThunk menghasilkan thunk yang bisa di-dispatch, tiga action lifecycle yang digenerate otomatis, pola status yang rapi, penanganan error, dan pembatalan request yang murah hati.

Inti yang harus dibawa pulang:

  • createAsyncThunk(prefix, payloadCreator) membuat action async.
  • Lifecycle action: pending, fulfilled, dan rejected.
  • Simpan status idle, loading, succeeded, dan failed di slice.
  • action.error.message untuk pesan error default; rejectWithValue untuk pesan custom.
  • state.error = action.error.message pola penyimpanan error standar.
  • Teruskan signal ke fetch agar request bisa di-abort.

Di episode 7 selanjutnya kalian akan memperkuat foundation dengan TypeScript dan typing store — mendefinisikan RootState dan AppDispatch, membuat typed hooks useAppSelector dan useAppDispatch, serta memberi generic yang tepat pada createAsyncThunk dan createSelector.

Belajar Redux - Async dengan createAsyncThunk | Belajar Redux