javascript - In React, failing to stop Axios request when component unmounts
问题描述
With data fetching in React, the following is a common warning:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function. in ParentComponent
I've read multiple posts and suggestions on how to handle this, and none are working currently.
For this, we have the function useAxiosApi
which fetches data asynchronously, and ParentComponent
which is the component that uses the useAxiosApi()
and needs the data. ParentComponent
is the component being unmounted / being referenced in the warnings.
Parent Component
import useAxiosApi...
function ParentComponent({ info }) {
const dataConfig = { season: info.season, scope: info.scope };
const [data, isLoading1, isError1] = useAxiosApi('this-endpoint', [], dataConfig);
return (
{isLoading && <p>We are loading...</p>}
{!isLoading &&
... use the data to render something...
}
)
}
useAxiosApi
import axios from 'axios';
import { useState } from 'react';
import useDeepCompareEffect from 'use-deep-compare-effect';
const resources = {};
const useAxiosApi = (endpoint, initialValue, config) => {
// Set Data-Fetching State
const [data, setData] = useState(initialValue);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
// Use in lieu of useEffect
useDeepCompareEffect(() => {
// Token/Source should be created before "fetchData"
let source = axios.CancelToken.source();
let isMounted = true;
// Create Function that makes Axios requests
const fetchData = async () => {
// For Live Search on keystroke, Save Fetches and Skip Fetch if Already Made
if (endpoint === 'liveSearch' && resources[config.searchText]) {
return [resources[config.searchText], false, false];
}
// Otherwise, Continue Forward
setIsError(false);
setIsLoading(true);
try {
const url = createUrl(endpoint, config);
const result = await axios.get(url, { cancelToken: source.token });
console.log('isMounted: ', isMounted);
if (isMounted) {
setData(result.data);
}
// If LiveSearch, store the response to "resources"
if (endpoint === 'liveSearch') {
resources[config.searchText] = result.data;
}
} catch (error) {
setIsError(true);
} finally {
setIsLoading(false);
}
};
// Call Function
fetchData();
// Cancel Request if needed in cleanup function
return () => {
console.log('Unmount or New Search? About to call source.cancel()');
isMounted = false; // is this doing its job?
source.cancel();
};
}, [endpoint, config]);
// Return as length-3 array
return [data, isLoading, isError];
};
export default useAxiosApi;
createUrl
is simply a function that takes the endpoint
and dataConfig
and creates the url that axios will fetch from. Note that our cancelTokens seem to be working in conjunction with the Live search, as new searches are cancelling the old search queries, and the saving of data results into resources
for the one specific endpoint liveSearch
works as well.
However, our problem is that when ParentComponent
is unmounted quickly, before the data fetch is complete, we still receive the Cant perform a React state update
warning. I've checked the console.logs()
, and console.log('isMounted: ', isMounted)
is always returning true, even if we unmount the component quickly after it is mounted / before data fetching is complete.
We're at a loss on this, as using the isMounted
variable is the way that I've seen this problem handled before. Perhaps there's a problem with the useDeepCompareEffect
hook? Or maybe we're missing something else.
Edit: Weve also tried to create the isMounted
variable from inside of ParentComponent
, and pass that as a parameter into the useAxiosApi
function, however this did not work for us either... In general, it would be much better if we can handle this warning via an update to our useAxiosApi
function, as opposed to in the ParentComponent
.
Edit2: It seems like the cancelToken
only works when a duplicate API call is fired off to the same endpoint. This is good for our liveSearch, however it means that all of the other fetches are not cancelled.
解决方案
推荐阅读
- r - 格式化分面饼图ggplot
- filepond - 如何使用 filepond 获取 exifdata(如 gps)?
- go - go encoding/pem 删除空白字符
- javascript - 如何添加猫鼬事务并创建文档?
- database - Pojo 类中对象列表的 Mybatis 属性映射器错误:spring Boot/mybatis
- kubernetes - Kubernetes v1.21.2:“selfLink 为空,无法引用”
- javascript - 矩阵变量在javascript中未定义
- tensorflow - 使用“tf.compat.v1.metrics.auc”的最佳实践?
- android - 模拟器或物理设备的 AOSP Android 12 Beta2 版本构建失败
- python - 使用 seaborn 创建多条曲线