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 startsfetchUsers.fulfilled- when request succeedsfetchUsers.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
- Use for API calls - Any async operation
- Three states - pending, fulfilled, rejected
- Handle in extraReducers - Not regular reducers
- Error handling - Use rejectWithValue for custom errors
- Access state/dispatch - Use second parameter helpers