【问题标题】:React useEffect Warning Warning: Can't perform a React state update on an unmounted componentReact useEffect Warning 警告:无法对未安装的组件执行 React 状态更新
【发布时间】:2021-12-03 15:15:16
【问题描述】:

这个组件有问题。

当我不断调用这个组件时,我收到了一条警告消息。

警告信息如下所示。

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.

我已经使用return方法清除了timerId,但我得到了这个文本。

如何修改这个组件中的useEffect?

我该怎么办?

下面是我的组件

import React, { useState, useEffect } from 'react';
import { Form, List, Button } from 'semantic-ui-react';
import axios from 'axios';

const Search = () => {

    const [term, setTerm] = useState('programming')

    const [debouncedTerm, setDebouncedTerm] = useState(term);
    const [results, setResults] = useState([]);

    useEffect(() => {
        const timerId = setTimeout(() => {
          setDebouncedTerm(term);
        }, 1000);
    
        return () => {
          clearTimeout(timerId);
        };
    }, [term]);

    useEffect(() => {
        
        const search = async () => {
            const {data} = await axios.get('https://en.wikipedia.org/w/api.php', {
                params: {
                    action: 'query',
                    list: 'search',
                    origin: '*',
                    format: 'json',
                    srsearch: debouncedTerm,
                },
            });

            setResults(data.query.search);

        };

        
        if(debouncedTerm){
            search();
        }

    }, [debouncedTerm])

    const handleClick = (pageid) => {
        window.open(`https://en.wikipedia.org?curid=${pageid}`);
    }

    const renderedResults = results.map((result) => {
        return (
          <List.Item key={result.pageid}>
                <Button floated='right' style={{"margin": "10px"}} onClick={() => handleClick(result.pageid)}>Go</Button>
                <List.Content>
                    <List.Header>{result.title}</List.Header>
                    {result.snippet}
                </List.Content>
          </List.Item>
        );
    });

    return (
        <React.Fragment>
            <Form> 
                <Form.Field>
                    <label>Search</label>
                    <input placeholder='Search Word' 
                            onChange={(e) => setTerm(e.target.value)}
                            value={term}
                    />
                </Form.Field>
            </Form>

            <List celled>
                {renderedResults}
            </List>
        </React.Fragment>    
    )
}

export default Search;

【问题讨论】:

  • 您确定是第一个触发警告的 useEffect 吗?而不是第二个? stackoverflow.com/questions/46110082/…
  • @Erik 警告消息有链接。当我点击它时,它会引导我到第一行代码const [term, setTerm] = useState('programming')
  • @Erik 我认为是第二个。我该如何解决?
  • 我不确定。您可以尝试在第一个 useEffect (设置 debouncedTerm 的地方)中进行 axios 调用。
  • 但是你并没有使用清理函数来实现 get 查询的第二个效果,所以使用 CancelToken 或 AbortController 在卸载时中止请求。

标签: reactjs components


【解决方案1】:

试试这个(axios v0.22.0+):

useEffect(() => {
    const controller= new AbortController();
      const search = async () => {
        try{
          const {data} = await axios.get('https://en.wikipedia.org/w/api.php', {
            params: {
                action: 'query',
                list: 'search',
                origin: '*',
                format: 'json',
                srsearch: debouncedTerm,
            },
            signal: controller.signal
          });

        setResults(data.query.search);
      }catch(err){
        if (!axios.isCancel(err)) throw err;
      }

    };

    
    if(debouncedTerm){
       search();
    }

    return ()=>{
       controller.abort();
    }

}, [debouncedTerm])

Related question

同样的事情,但具有自动清理能力,可以使用我的库 (Generic fetch demo) 来完成:

import { useAsyncEffect } from "use-async-effect2";
import cpAxios from "cp-axios";
// ...
useAsyncEffect(function*(){
    if(!debouncedTerm){
       return;
    }
    
    const {data} = yield cpAxios.get('https://en.wikipedia.org/w/api.php', {
            params: {
                action: 'query',
                list: 'search',
                origin: '*',
                format: 'json',
                srsearch: debouncedTerm,
            }
        });

    setResults(data.query.search);     
}, [debouncedTerm])

这可以简化为:

import { useAsyncEffect } from "use-async-effect2";
import cpAxios from "cp-axios";
// ...
const [cancel, done, results, err]= useAsyncEffect(function*(){
    if(!debouncedTerm){
       return;
    }
    
    const {data} = yield cpAxios.get('https://en.wikipedia.org/w/api.php', {
            params: {
                action: 'query',
                list: 'search',
                origin: '*',
                format: 'json',
                srsearch: debouncedTerm,
            }
        });

    return data.query.search;     
}, { states: true, deps: [debouncedTerm] });

console.log('results state:', results);

【讨论】:

  • 我尝试使用 AbortController 但警告信息仍然存在。
  • 您确定您使用的是 Axios v0.22.0 或更高版本吗?在 v0.22.0 中添加了 AbortController 支持。
  • 我在 useEffect 中使用 AbortController 后收到此警告消息(未捕获(承诺中)取消 {message: 'canceled'})。
  • 没关系。这是一个未捕获的 axios Cancel 拒绝,因此如果您不想在控制台中看到此类警告,则应使用 try...catch 捕获此类错误(我已更新代码示例)
猜你喜欢
  • 2023-03-06
  • 2021-03-27
  • 2019-09-11
  • 2021-08-07
  • 2020-06-14
  • 1970-01-01
  • 2021-02-24
  • 2022-01-18
相关资源
最近更新 更多