Skip to main content

Async Actions with createAsyncThunk

When you need to fetch data from an API or do other async work, use createAsyncThunk. It handles loading states automatically.

Simple Example

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'

// Create async action
const fetchUsers = createAsyncThunk(
'users/fetchUsers',
async () => {
const response = await fetch('/api/users')
return response.json()
}
)

// Use in slice
const usersSlice = createSlice({
name: 'users',
initialState: {
users: [],
loading: false,
error: null,
},
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = true
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.loading = false
state.users = action.payload
})
.addCase(fetchUsers.rejected, (state, action) => {
state.loading = false
state.error = action.error.message
})
},
})

How It Works

When you call fetchUsers(), Redux Toolkit automatically creates three actions:

  • fetchUsers.pending - when request starts
  • fetchUsers.fulfilled - when request succeeds
  • fetchUsers.rejected - when request fails

With Parameters

const fetchUserById = createAsyncThunk(
'users/fetchById',
async (userId) => {
const response = await fetch(`/api/users/${userId}`)
return response.json()
}
)

// Use it
dispatch(fetchUserById(123))

Error Handling

const createPost = createAsyncThunk(
'posts/create',
async (postData, { rejectWithValue }) => {
try {
const response = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(postData),
})

if (!response.ok) {
throw new Error('Failed to create post')
}

return response.json()
} catch (error) {
return rejectWithValue(error.message)
}
}
)

Using Extra Info

const updatePost = createAsyncThunk(
'posts/update',
async (postData, { getState, dispatch }) => {
const state = getState()
const token = state.auth.token

const response = await fetch(`/api/posts/${postData.id}`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(postData),
})

return response.json()
}
)

Complete Example

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'

// Async actions
export const fetchTodos = createAsyncThunk(
'todos/fetchTodos',
async () => {
const response = await fetch('/api/todos')
return response.json()
}
)

export const addTodo = createAsyncThunk(
'todos/addTodo',
async (todoText) => {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: todoText, completed: false }),
})
return response.json()
}
)

// Slice
const todosSlice = createSlice({
name: 'todos',
initialState: {
items: [],
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null,
},
reducers: {
toggleTodo: (state, action) => {
const todo = state.items.find(item => item.id === action.payload)
if (todo) {
todo.completed = !todo.completed
}
},
},
extraReducers: (builder) => {
builder
// Fetch todos
.addCase(fetchTodos.pending, (state) => {
state.status = 'loading'
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.status = 'succeeded'
state.items = action.payload
})
.addCase(fetchTodos.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
// Add todo
.addCase(addTodo.fulfilled, (state, action) => {
state.items.push(action.payload)
})
},
})

export const { toggleTodo } = todosSlice.actions
export default todosSlice.reducer

Using in Components

import { useSelector, useDispatch } from 'react-redux'
import { fetchTodos, addTodo } from './todosSlice'

function TodoList() {
const dispatch = useDispatch()
const { items, status, error } = useSelector(state => state.todos)

useEffect(() => {
if (status === 'idle') {
dispatch(fetchTodos())
}
}, [status, dispatch])

const handleAddTodo = (text) => {
dispatch(addTodo(text))
}

if (status === 'loading') return <div>Loading...</div>
if (status === 'failed') return <div>Error: {error}</div>

return (
<div>
{items.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
)
}

Key Points

  1. Use for API calls - Any async operation
  2. Three states - pending, fulfilled, rejected
  3. Handle in extraReducers - Not regular reducers
  4. Error handling - Use rejectWithValue for custom errors
  5. Access state/dispatch - Use second parameter helpers