Asynchronous Operations with React

Ganesh JaiwalGanesh Jaiwal
6 min read

Asynchronous operations are a crucial aspect of modern web applications, and React, as a widely-used JavaScript library for building user interfaces, provides a flexible environment for handling such operations. This blog will discuss the implementation of asynchronous operations in React, focusing on data fetching via the Fetch API and Axios, as well as managing side effects through Redux middleware, specifically Thunk and Saga.

Fetching Data with the Fetch API

The Fetch API is a built-in JavaScript function for making network requests. It offers a modern way to handle asynchronous HTTP requests. When used in a React application, it can be invoked within lifecycle methods or hooks such as useEffect.

Basic Usage of Fetch

To illustrate the Fetch API, consider a simple example where we fetch user data from a public API and display it in a React component.

import React, { useEffect, useState } from 'react';

const UserList = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const response = await fetch('<Users API URL>');
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

export default UserList;

Explanation

In the example above:

  1. State Management: We initialize three state variables using the useState hook—users to hold the fetched data, loading to keep track of the loading state, and error to capture any potential errors during fetching.

  2. Data Fetching: The fetchUsers function is defined as an asynchronous function using the async/await syntax to make it more readable. We handle the response status and potential errors effectively.

  3. Effect Hook: The useEffect hook is utilized to trigger the fetch operation after the initial render, ensuring that the fetching does not block the rendering of the component.

By using the Fetch API, we can manage asynchronous data requests efficiently. However, if you require more advanced functionality, such as automatic request cancellation, progress tracking, or better error handling, you may consider using Axios.

Using Axios for API Calls

Axios is a widely adopted third-party library that simplifies HTTP requests in JavaScript applications. Its features enhance robustness in API calls and improve the overall development experience.

Basic Usage of Axios

Here is the same example using Axios instead of the Fetch API:

import React, { useEffect, useState } from 'react';
import axios from 'axios';

const UserList = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const response = await axios.get('<Users API URL>');
        setUsers(response.data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
};

export default UserList;

Explanation

The main changes in this code involve the use of Axios for making the GET request. The advantages of using Axios include:

  • Automatic JSON Transformation: Axios automatically transforms JSON request and response data, reducing the need for manual parsing.

  • Interceptors: Axios supports request and response interceptors, allowing for global error handling and request modification.

  • Cancel Requests: Axios allows you to cancel requests, which can be beneficial while navigating between components or when a user is interacting with the UI.

As one can see, Axios provides a more streamlined approach to handling HTTP requests compared to the Fetch API, especially for applications requiring more complex data handling.

Handling Side Effects with Redux Middleware

In larger applications, managing asynchronous operations can become challenging, necessitating a robust state management solution. Redux, combined with middleware such as Thunk or Saga, offers a structured way to handle side effects.

Redux Thunk

Redux Thunk is a middleware that allows action creators to return a function instead of an action. This function can execute side effects like API calls.

Example using Thunk

// actions/userActions.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

const fetchUsers = () => {
  return async (dispatch) => {
    dispatch({ type: 'FETCH_USERS_REQUEST' });
    try {
      const response = await fetch('<Users API URL>');
      const data = await response.json();
      dispatch({ type: 'FETCH_USERS_SUCCESS', payload: data });
    } catch (error) {
      dispatch({ type: 'FETCH_USERS_FAILURE', payload: error.message });
    }
  };
};

// Basic Reducer
const userReducer = (state = { users: [], loading: false, error: null }, action) => {
  switch (action.type) {
    case 'FETCH_USERS_REQUEST':
      return { ...state, loading: true };
    case 'FETCH_USERS_SUCCESS':
      return { users: action.payload, loading: false };
    case 'FETCH_USERS_FAILURE':
      return { ...state, loading: false, error: action.payload };
    default:
      return state;
  }
};

// Store
const store = createStore(userReducer, applyMiddleware(thunk));

Explanation

  1. Action Creators: The fetchUsers function returns a thunk that dispatches multiple actions. The first action indicates the start of data fetching, while the success or failure actions handle the response.

  2. Reducer: The reducer updates the application state based on actions dispatched during the async operation.

  3. Store: The Redux store is created with the thunk middleware, enabling async actions to be dispatched.

Redux Saga

Redux Saga is another middleware for managing side effects, using generator functions to handle complex async flows efficiently.

Example using Redux Saga

import { call, put, takeEvery } from 'redux-saga/effects';

// API call
const fetchUsersApi = async () => {
  const response = await fetch('<Users API URL>');
  return response.json();
};

// Worker Saga
function* fetchUsers() {
  try {
    const users = yield call(fetchUsersApi);
    yield put({ type: 'FETCH_USERS_SUCCESS', payload: users });
  } catch (error) {
    yield put({ type: 'FETCH_USERS_FAILURE', payload: error.message });
  }
}

// Watcher Saga
function* watchFetchUsers() {
  yield takeEvery('FETCH_USERS_REQUEST', fetchUsers);
}

// Root Saga
export default function* rootSaga() {
  yield all([watchFetchUsers()]);
}

Explanation

  1. Saga Function: The fetchUsers saga handles the async operation, yielding the call effect to invoke the API call.

  2. Handling Side Effects: By using put, the saga allows for dispatching actions based on the result of the API call.

  3. Watcher Saga: The watcher saga listens for FETCH_USERS_REQUEST and triggers the fetchUsers worker saga.

Conclusion

In conclusion, handling asynchronous operations in React can be managed effectively using the Fetch API or Axios for data fetching. For larger applications that involve state management, Redux middleware such as Thunk and Saga provides a structured approach to managing side effects. By leveraging these tools, developers can enhance the robustness and maintainability of their React applications, ensuring a seamless user experience. As the complexity of an application grows, understanding and implementing these asynchronous strategies becomes essential for delivering responsive and user-friendly interfaces.

0
Subscribe to my newsletter

Read articles from Ganesh Jaiwal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ganesh Jaiwal
Ganesh Jaiwal

Hello! I'm a dedicated software developer with a passion for coding and a belief in technology's impact on our world. My programming journey started a few years ago and has reshaped my career and mindset. I love tackling complex problems and creating efficient code. My skills cover various languages and technologies like JavaScript, Angular, ReactJS, NodeJs, and Go Lang. I stay updated on industry trends and enjoy learning new tools. Outside of coding, I cherish small routines that enhance my workday, like sipping tea, which fuels my creativity and concentration. Whether debugging or brainstorming, it helps me focus. When I'm not coding, I engage with fellow developers. I value teamwork and enjoy mentoring newcomers and sharing my knowledge to help them grow. Additionally, I explore the blend of technology and creativity through projects that incorporate art and data visualization. This keeps my perspective fresh and my passion alive. I'm always seeking new challenges, from open-source contributions to hackathons and exploring AI. Software development is more than a job for me—it's a passion that drives continuous learning and innovation.