Handling Asynchronous Actions with createAsyncThunk

Exemplify the use of createAsyncThunk from Redux Toolkit for handling asynchronous operations, replacing the need for manual action types and thunk actions.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

// Define the initial state of the feature
const initialState = {
    data: null,
    isLoading: false,
    error: null

// Create an async thunk for fetching data
export const fetchData = createAsyncThunk(
    'data/fetchData', // action type prefix
    async (arg, { rejectWithValue }) => {
        try {
            const response = await fetch('');
            if (!response.ok) {
                throw new Error('Network response was not ok');
            const data = await response.json();
            return data;
        } catch (error) {
            return rejectWithValue(error.message);

// Slice to handle the state and the reducers
export const dataSlice = createSlice({
    name: 'data',
    reducers: {},
    extraReducers: (builder) => {
            .addCase(fetchData.pending, (state) => {
                state.isLoading = true;
            .addCase(fetchData.fulfilled, (state, action) => {
                state.isLoading = false;
       = action.payload;
            .addCase(fetchData.rejected, (state, action) => {
                state.isLoading = false;
                state.error = action.payload;

export default dataSlice.reducer;
This code defines a slice that uses createAsyncThunk to perform an asynchronous fetch operation. The initialState is defined with properties to track loading status, the fetched data, and potential errors. The fetchData async thunk is defined to make a network request; it dispatches actions based on the returned promise: pending, fulfilled, or rejected. The addCase method in the slice's extraReducers field responds to these action types by setting the appropriate state.