Home > Back-end >  How to cancel a dispatched axios request from reducer
How to cancel a dispatched axios request from reducer

Time:01-21

I have used useEffects in my components to load data, moment the component mounts. But i am trying to optimize my code by avoiding any memmory leaks. To achieve this i am trying to use AbortController to cancel any request in any case if the component unmounts. Something like this

useEffect(() => {
         let abortController;
        (async () {
             abortController = new AbortController();
             let signal = abortController.signal;    

             // the signal is passed into the request(s) we want to abort using this controller
             const { data } = await axios.get(
                 'https://random-data-api.com/api/company/random_company',
                 { signal: signal }
             );
             setCompany(data);
        })();

        return () => abortController.abort();
    }, []);

But i am finding it difficult to implement this because my axios request is in a service file which is called by a reducer in slice file. Below is my useEffect of my Component.

// Component.js
import { bookDetails } from '../../features/user/userSlice'
//import reducer from my slice file
.
.
// code 
useEffect(() => {
        let mounted = true
        if (mounted) {
            dispatch(bookDetails(bookId))
        }
        return () => mounted = false
    }, [])

Below is my reducer from my slice file which imports function from my service file.

// userSlice.js
import userService from "./userService";

export const bookDetails = createAsyncThunk(
  "user/book",
  async (id, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      return await userService.bookDetails({ id, token });
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

Below is my function from my service file

// userService.js
const bookDetails = async ({ id, token }) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  };
  const response = await axios.get(API_URL   `/book/${id}`, config);
  return response.data;
};

I want to cancel this request in case my component unmounts from useEffect. Please Help. Thanks in advance.

CodePudding user response:

Since you're using Redux Toolkit's createAsyncThunk, it sounds like you're looking for the "canceling while running" feature of createAsyncThunk. Perhaps something like the following:

export const bookDetails = createAsyncThunk(
  "user/book",
  async (id, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      return await userService.bookDetails({ id, token, signal: thunkAPI.signal });
    } catch (error) {
      return thunkAPI.rejectWithValue(getErrorMessage(error));
    }
  }
);

useEffect(() => {
  const promise = dispatch(bookDetails(bookId));
  return () => promise.abort();
}, [])

However, if all you're worried about is fetching data, I strongly suggest taking a look at RTK Query, React Query, or SWR. These take care of the common pattern of asynchronously fetching data, without making you write the various slices and reducers yourself, and add useful features such as caching, retrying, etc.

CodePudding user response:

Make your bookDetails method accept an extra property named signal. Then pass it to the axios method.

const bookDetails = async ({ id, token, signal }) => {
  const config = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
    signal,
  };
  const response = await axios.get(API_URL   `/book/${id}`, config);
  return response.data;
};

For this purpose, change your reducer method like this:

async ({ id, signal}, thunkAPI) => {
    try {
      const token = thunkAPI.getState().auth.user.token;
      return await userService.bookDetails({ id, token, signal });
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }

Finally dispatch actions like this:

abortController = new AbortController();
let signal = abortController.signal;               

dispatch(bookDetails({ id: bookId, signal: signal ))

CodePudding user response:

it's a good idea to have an AbortController to avoid memory leaks when using useEffect to load data this will be useful when its possible to get errors during dispatching requests to avoid requests still running when an error occurs, here's an example that you can use to cancel requests if the state doesn't load for any reason.

import { useEffect, useState } from 'react';

const MyComponent = () => {
  const [data, setData] = useState(null); // set state to null

  useEffect(() => {
    const controller = new AbortController(); // AbortController instance

    // Dispatch the request
    fetch('/data', { signal: controller.signal })
      .then(response => response.json()) // get response
      .then(data => setData(data)) // Updating useState
      .catch(e => { // Catch errors
        if (e.name === 'AbortError') {
          console.log("Failed to fetching data");
        }
        throw e;
      });

    // cleanup controller
    return () => {
      controller.abort();
    };
  }, []);

  // Render the component
  return <div>{data && data.message}</div>;
};
  • Related