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 状态更新

无法对未安装的组件执行 React 状态更新。 useEffect React-Native

反应钩子。无法对未安装的组件执行 React 状态更新

“警告:无法对未安装的组件执行 React 状态更新。” [关闭]

无法使用 fetch POST 方法对未安装的组件执行 React 状态更新

useEffect 用于设置输入值时抛出错误