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>;
};