1

I have a problem with this component.

When I call this component constantly, I got a warning message.

Here is the warning message is shown below.

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.

I already used the return method to clear timerId but I got this text.

How can I revise useEffect in this component?

What should I do?

Here is my component shown below

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;
S.N
  • 2,157
  • 3
  • 29
  • 78
  • Are you sure it is the first useEffect that triggers the warning? And not the second one? https://stackoverflow.com/questions/46110082/cancel-async-request-on-unmount-with-axios/53711492 – Erik Oct 15 '21 at 12:17
  • @Erik The warning message has link. When I click it, it leads me to first line of code `const [term, setTerm] = useState('programming') `. – S.N Oct 15 '21 at 12:36
  • @Erik I think second one. How can I fix it? – S.N Oct 16 '21 at 19:17
  • Im not sure. You can try to make the axios call in the first useEffect (where you are setting debouncedTerm). – Erik Oct 17 '21 at 06:58
  • But you're not using the cleanup function for the second effect with get query, so use CancelToken or AbortController to abort the request on unmount. – Dmitriy Mozgovoy Oct 17 '21 at 17:07
  • @DmitriyMozgovoy How can I clean up the second useEffect? – S.N Oct 18 '21 at 11:28

1 Answers1

2

Try this (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

The same thing, but with auto cleanup ability, can be done using my libs (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])

This can be simplified to:

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);
Dmitriy Mozgovoy
  • 1,419
  • 2
  • 8
  • 7
  • I tried to use AbortController but the warning message still exists. – S.N Oct 18 '21 at 22:11
  • Are you sure you are using Axios v0.22.0 or higher? AbortController support was added in v0.22.0. – Dmitriy Mozgovoy Oct 19 '21 at 10:33
  • I got this warning message (Uncaught (in promise) Cancel {message: 'canceled'}) after I used AbortController in useEffect. – S.N Oct 19 '21 at 11:25
  • It's ok. This is an uncaught axios Cancel rejection, so you should catch this kind of error with try...catch if you don't want to see such kind of warning in the console (I've updated the code example) – Dmitriy Mozgovoy Oct 19 '21 at 16:00