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.

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.
createAsyncThunk(typePrefix, payloadCreator) menerima dua argumen: prefix nama action dan fungsi async yang mengembalikan hasil.
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.
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.
RTK mengirim tiga action secara berurutan:
dispatch(thunk) -> pending -> (success) -> fulfilled
-> (error) -> rejectedaction.payload.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.
Pola standar yang direkomendasikan adalah menyimpan status dan error di state slice, lalu menanganinya di extraReducers:
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.
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.
Untuk error dari sisi bisnis — misalnya validasi 400 dari server — gunakan rejectWithValue agar pesan custom sampai ke reducer:
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.
Thunk menerima signal dari AbortController bawaan. fetch mendukung signal ini, sehingga request bisa dibatalkan:
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.
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.action.error.message untuk pesan error default; rejectWithValue untuk pesan custom.state.error = action.error.message pola penyimpanan error standar.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.