Register to get access to free programming courses with interactive exercises

Asynchronous requests via Thunk React: Redux Toolkit

One of the most complicated tasks in building front-end applications is dealing with external requests. Difficulties come from two sides.

On the one hand, asynchrony itself generates ambiguities, so the standard mechanisms stop working. Redux doesn't know how to work asynchronously, so all the processing of requests takes place outside Redux. In this case, any non-trivial logic for processing asynchronous actions will appear inside React components:

const MyComponent = (props) => {
  const onClick = async (todoId) => {
    const response = await axios.get(`https://ru.hexlet.io/api/todos/${todoId}`);
    const todos = // Retrieving and converting data from the response
    dispatch(todosLoaded(todos));
  };

  // Rendering
};

On the other hand, the network is an unreliable thing. Requests can take a long time or fail, so we should monitor them to ensure we get the correct response. If the request takes a long time, we show a spinner. If the request is interrupted, we display the corresponding warning:

const onClick = async (todoId) => {
  // Working with the finite state machine to process any HTTP request
  try {
    // Starting the loading process
    dispatch(todosLoadingStarted());
    const response = await axios.get(`https://hexlet.io/api/todos/${todoId}`);
    const todos = // Extracting and transforming data from the response
    dispatch(todosLoaded(todos));
  } catch (e) {
    // It's even more complicated because we have to keep track of exactly what went wrong
    dispatch(todosLoadingFailed(e.message));
  }
};

We write a lot of similar code, even for a few calls. In real applications, the number of calls could be dozens or hundreds. Therefore, you can't do without a ready-made solution. To automate HTTP requests, we need two mechanisms:

  • The redux-thunk middleware, already included in Redux Toolkit
  • The createAsyncThunk() mechanism

The redux-thunk middleware allows asynchronous code within dispatch(). That way, we can bring the logic of queries and storage updates into separate functions called thunks:

// Note the nested function receives `dispatch`
// This function will be stored outside the component in a slice, for example
export const fetchTodoById = (todoId) => async (dispatch) => {
  const response = await axios.get(`https://hexlet.io/todos/${todoId}`);
  // We need to perform the necessary normalization and handle errors
  dispatch(todosLoaded(response.todos));
}

// The usage
const TodoComponent = ({ todoId }) => {
  const dispatch = useDispatch();

  const onFetchClicked = () => {
    // Passing the asynchronous function
    dispatch(fetchTodoById(todoId));
  };

  // Around here, we use `onFetchClicked`
}

You can do roughly the same thing without redux-thunk just by writing an asynchronous function to which we pass dispatch() as input. The difference shows up in the more advanced use cases. For example, when we work with state or global objects, such as a WebSocket connection. In this case, you have to use redux-thunk, which makes this all much easier to do:

// `(dispatch, getState, extraArgument)`
export const fetchTodoById = (todoId) => async (dispatch, getState, extraArgument) => {
  // Here is any data transmitted during the middleware configuration phase
  const { serviceApi } = extraArgument;
  const response = await serviceApi.getTodo(todoId);
  dispatch(todosLoaded(response.todos));
};

Despite the convenience gained, thunks do not reduce the amount of code, and the same error handling will make up a large part of the code. This is where createAsyncThunk():

import { createAsyncThunk, createSlice, createEntityAdapter } from '@reduxjs/toolkit';
// To avoid hardcoding URLs, we can create a module and create URLs in it
import { getUserUrl } from './routes.js';

// Creating our thunk
export const fetchUserById = createAsyncThunk(
  'users/fetchUserById', // It is displayed in dev tools and must be unique for everyone
  async (userId) => {
    // Only query logic and data return here
    // No error handling
    const response = await axios.get(getUserUrl(userId));
    return response.data;
  }
);

const usersAdapter = createEntityAdapter();

const usersSlice = createSlice({
  name: 'users',
  // Adding loading process tracking to the state
  // { ids: [], entities: {}, loading: 'idle', error: null }
  initialState: usersAdapter.getInitialState({ loading: 'idle', error: null }),
  reducers: {
    // Here are any reducers we need
  },
  extraReducers: (builder) => {
    builder
      // We call it just before the execution of the query
      .addCase(fetchUserById.pending, (state) => {
        state.loading = 'loading';
        state.error = null;
      })
      // We call this if the query is successful
      .addCase(fetchUserById.fulfilled, (state, action) => {
        // Adding a user
        usersAdapter.addOne(state, action.payload);
        state.loading = 'idle';
        state.error = null;
      })
      // We call it in case of an error
      .addCase(fetchUserById.rejected, (state, action) => {
        state.loading = 'failed';
        // https://redux-toolkit.js.org/api/createAsyncThunk#handling-thunk-errors
        state.error = action.error;
      });
  },
})

// Somewhere in the application
import { fetchUserById } from './slices/usersSlice.js';

// Inside the component
dispatch(fetchUserById(123));

Each thunk created with createAsyncThunk() contains three reducers: pending, fulfilled, and rejected. They correspond to promise states and are called by Redux Toolkit the moment the promise enters one of these states. We don't have to respond to all, so we can choose what is important to us in the application.


Recommended materials

  1. How Redux Thunk Works
  2. createAsyncThunk documentation
  3. RTK

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

About Hexlet learning process

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs
profession
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.