React useEffect 抛出错误:无法对未安装的组件执行 React 状态更新
Posted
技术标签:
【中文标题】React useEffect 抛出错误:无法对未安装的组件执行 React 状态更新【英文标题】:React useEffect throws error: Can't perform a React state update on an unmounted component 【发布时间】:2021-09-28 16:01:24 【问题描述】:我在我的 spotify api 应用程序中使用 react-native,当我想使用 axios
(在 useEffect
中,因为我想在组件加载时呈现返回的项目)从我的服务器获取数据时,它会抛出错误: 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
。所以如果有人知道如何解决这个问题,我会非常感激。
import React, useEffect, useState from 'react'
import View, Text, StyleSheet, Image, FlatList, ActivityIndicator from 'react-native'
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
export default function ArtistsCom(route)
const type = route.name.toLowerCase();
const [time, setTime] = useState('short_term');
const [access_token, setAccess_token] = useState('');
const [refresh_token, setRefresh_token] = useState('');
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() =>
setLoading(true);
AsyncStorage.getItem('refresh_token')
.then(value => setRefresh_token(value));
const getDataAsync = async () =>
const res = await axios.post(`http://localhost:3001/api/refresh`,
headers:
body:
refresh_token: refresh_token
)
console.log(res);
setAccess_token(res.data.access_token);
return res;
;
getDataAsync();
, []);
return (
<View>
<View>
!loading ? items.map((item, key) =>
return (
<View key=key style=width: 150, height: 150, margin: 10>
<Image style=width: '100%', height: '100%' source=uri: type == 'artists' ? item.images[0].url : item.album.images[0].url />
<View style=position: 'absolute', bottom: 0, left:4, height: 20, width: '100%', backgroudColor: 'red'><Text style=color: 'white'>key+1. item.name</Text></View>
</View>
)
) : <Text>Loading...</Text>
</View>
</View>
)
【问题讨论】:
这是您的完整代码吗?当您从存储中检索刷新令牌时,我看到的唯一状态更新是setRefresh_token(value)
。如果是 axios 调用,那么您可以使用取消令牌并在组件卸载时终止正在进行的请求。
这是我的错。在代码中我没有setItems()
。问题出在getDataAsync()
(我认为)
【参考方案1】:
您可以使用取消令牌来取消正在进行的请求。
useEffect(() =>
...
// Create cancel token and source
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const getDataAsync = async () =>
const res = await axios.post(
`http://localhost:3001/api/refresh`,
cancelToken: source.token, // <-- attach cancel token to request
headers:
body:
refresh_token: refresh_token
,
);
console.log(res);
setItems(res.data.items);
return res;
;
getDataAsync();
// Return useEffect cleanup function to cancel request
return () =>
source.cancel('Component unmounted'); // message is optional
;
, []);
更新
将异步逻辑包含在 try/catch/finally
中以发出多个请求并处理任何被拒绝的承诺和错误。
useEffect(() =>
// Create cancel token and source
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const getDataAsync = async () =>
setLoading(true);
try
const refresh_token = await AsyncStorage.getItem('refresh_token');
const res = await axios.post(
`http://localhost:3001/api/refresh`,
cancelToken: source.token, // <-- attach cancel token to request
headers:
body:
refresh_token
,
);
const access_token = res.data;
setAccess_token(access_token);
const res2 = await axios.get(`http://localhost:3001/api/top/$type?time_range=$time&limit=50&access_token=$access_token`);
setItems(res2.data.items);
catch(error)
// handle any errors, log them, set some state, etc...
finally
setLoading(false);
;
getDataAsync();
// Return useEffect cleanup function to cancel request
return () =>
source.cancel('Component unmounted'); // message is optional
;
, [refreshToken]);
【讨论】:
我做了完全一样的,但仍然是同样的错误。 @Vichmi 你确定这是卸载后尝试更新状态的代码吗? axios 请求应该返回 access_token: token 但它给了我错误。 @Vichmi 啊,我想我看到了部分问题。最初,您同时发生了 2 个异步“操作”,一个用于获取访问令牌,另一个用于使用访问令牌并发出请求。第二个没有等第一个。我部分基于此更新了我的答案,部分基于您在答案中添加的附加代码,但仍然使用async/await
语法而不是 Promise 链。【参考方案2】:
我想通了
代码如下:
const type = route.name.toLowerCase();
const [time, setTime] = useState('short_term');
const [access_token, setAccess_token] = useState('');
const [refresh_token, setRefresh_token] = useState('');
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const getItems = (ACCESS_TOKEN) =>
axios.get(`http://localhost:3001/api/top/$type?time_range=$time&limit=50&access_token=$ACCESS_TOKEN`)
.then(res =>
setItems(res.data.items);
setLoading(false);
)
useEffect(() =>
setLoading(true);
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
const getRefreshedAccessToken = async() =>
axios.post(`http://localhost:3001/api/refresh`,
cancelToken: source.token,
headers:
body:
refresh_token: await AsyncStorage.getItem('refresh_token')
)
.then(res =>
console.log(res.data.access_token);
setAccess_token(res.data.access_token);
getItems(res.data.access_token);
)
.catch(err => console.log(err))
;
getRefreshedAccessToken();
return () =>
source.cancel('Component unmounted');
, [refresh_token]);
【讨论】:
这是部分解决方案,因为您还应该在组件卸载时取消第二个请求。以上是关于React useEffect 抛出错误:无法对未安装的组件执行 React 状态更新的主要内容,如果未能解决你的问题,请参考以下文章
无法对未安装的组件执行 React 状态更新。 useEffect React-Native
“警告:无法对未安装的组件执行 React 状态更新。” [关闭]