web-dev-qa-db-fra.com

Annulation d'un Axios REST appel dans React Crochets useEffects échec du nettoyage

Évidemment, je ne nettoie pas correctement et j'annule la requête GET axios comme je devrais être. Sur mon local, je reçois un avertissement qui dit

Impossible d'effectuer une mise à jour d'état React sur un composant non monté. Il s'agit d'un no-op, mais il indique une fuite de mémoire dans votre application. Pour corriger, annulez tous les abonnements et tâches asynchrones dans un Fonction de nettoyage useEffect.

Sur stackblitz, mon code fonctionne, mais pour une raison quelconque, je ne peux pas cliquer sur le bouton pour afficher l'erreur. Il montre toujours toujours les données retournées.

https://codesandbox.io/s/8x5lzjmwl8

Veuillez consulter mon code et trouver mon défaut.

useAxiosFetch.js

import {useState, useEffect} from 'react'
import axios from 'axios'

const useAxiosFetch = url => {
    const [data, setData] = useState(null)
    const [error, setError] = useState(null)
    const [loading, setLoading] = useState(true)

    let source = axios.CancelToken.source()
    useEffect(() => {
        try {
            setLoading(true)
            const promise = axios
                .get(url, {
                    cancelToken: source.token,
                })
                .catch(function (thrown) {
                    if (axios.isCancel(thrown)) {
                        console.log(`request cancelled:${thrown.message}`)
                    } else {
                        console.log('another error happened')
                    }
                })
                .then(a => {
                    setData(a)
                    setLoading(false)
                })
        } catch (e) {
            setData(null)
            setError(e)
        }

        if (source) {
            console.log('source defined')
        } else {
            console.log('source NOT defined')
        }

        return function () {
            console.log('cleanup of useAxiosFetch called')
            if (source) {
                console.log('source in cleanup exists')
            } else {
                source.log('source in cleanup DOES NOT exist')
            }
            source.cancel('Cancelling in cleanup')
        }
    }, [])

    return {data, loading, error}
}

export default useAxiosFetch

index.js

import React from 'react';

import useAxiosFetch from './useAxiosFetch1';

const index = () => {
    const url = "http://www.fakeresponse.com/api/?sleep=5&data={%22Hello%22:%22World%22}";
    const {data,loading} = useAxiosFetch(url);

    if (loading) {
        return (
            <div>Loading...<br/>
                <button onClick={() => {
                    window.location = "/awayfrom here";
                }} >switch away</button>
            </div>
        );
    } else {
        return <div>{JSON.stringify(data)}xx</div>
    }
};

export default index;
6
Peter Kellner

Voici le code final avec tout fonctionnant au cas où quelqu'un d'autre reviendrait.

import {useState, useEffect} from "react";
import axios, {AxiosResponse} from "axios";

const useAxiosFetch = (url: string, timeout?: number) => {
    const [data, setData] = useState<AxiosResponse | null>(null);
    const [error, setError] = useState(false);
    const [errorMessage, setErrorMessage] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        let unmounted = false;
        let source = axios.CancelToken.source();
        axios.get(url, {
            cancelToken: source.token,
            timeout: timeout
        })
            .then(a => {
                if (!unmounted) {
                    // @ts-ignore
                    setData(a.data);
                    setLoading(false);
                }
            }).catch(function (e) {
            if (!unmounted) {
                setError(true);
                setErrorMessage(e.message);
                setLoading(false);
                if (axios.isCancel(e)) {
                    console.log(`request cancelled:${e.message}`);
                } else {
                    console.log("another error happened:" + e.message);
                }
            }
        });
        return function () {
            unmounted = true;
            source.cancel("Cancelling in cleanup");
        };
    }, []);

    return {data, loading, error, errorMessage};
};

export default useAxiosFetch;
5
Peter Kellner

Le problème dans votre cas est que sur un réseau rapide, les demandes entraînent une réponse rapide et ne vous permettent pas de cliquer sur le bouton. Sur un réseau étranglé que vous pouvez réaliser via ChromeDevTools, vous pouvez visualiser correctement ce comportement

Deuxièmement, lorsque vous essayez de vous éloigner en utilisant window.location.href = 'away link' react n'a pas de changement pour déclencher/exécuter le nettoyage du composant et donc la fonction de nettoyage de useEffect ne sera pas déclenchée.

Utilisation du routeur fonctionne

import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom'

import useAxiosFetch from './useAxiosFetch'

function App(props) {
  const url = 'https://www.siliconvalley-codecamp.com/rest/session/arrayonly'
  const {data, loading} = useAxiosFetch(url)

  // setTimeout(() => {
  //   window.location.href = 'https://www.google.com/';
  // }, 1000)
  if (loading) {
    return (
      <div>
        Loading...
        <br />
        <button
          onClick={() => {
            props.history.Push('/home')
          }}
        >
          switch away
        </button>
      </div>
    )
  } else {
    return <div>{JSON.stringify(data)}</div>
  }
}

ReactDOM.render(
  <Router>
    <Switch>
      <Route path="/home" render={() => <div>Hello</div>} />
      <Route path="/" component={App} />
    </Switch>
  </Router>,
  document.getElementById('root'),
)

Vous pouvez check the demo fonctionne correctement sur un réseau lent

2
Shubham Khatri