27
votes

React useEffect provoquant: impossible d'effectuer une mise à jour de l'état de React sur un composant non monté

Lors de la récupération des données que j'obtiens: impossible d'effectuer une mise à jour de l'état React sur un composant non monté. L'application fonctionne toujours, mais React suggère que je pourrais causer une fuite de mémoire.

"Il s'agit d'un non-op, mais cela indique une fuite de mémoire dans votre application. Pour corriger, annulez tous les abonnements et les tâches asynchrones dans une fonction de nettoyage useEffect."

Pourquoi est-ce que je continue de recevoir cet avertissement?

J'ai essayé de rechercher ces solutions:

https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal

https://developer.mozilla.org/en-US/docs/Web/API/AbortController

mais cela me donnait toujours l'avertissement.

this.getArtistProfile = (id,includeGroups,market,limit,offset) => {
  return Promise.all([
    this.getArtist(id),
    this.getArtistAlbums(id,includeGroups,market,limit,offset),
    this.getArtistTopTracks(id,market)
  ])
  .then(response => {
    return ({
      artist: response[0],
      artistAlbums: response[1],
      artistTopTracks: response[2]
    })
  })
}

Éditer:

Dans mon fichier api, j'ai ajouté un AbortController() et utilisé un signal pour que je puisse annuler une demande.

export function spotifyAPI() {
  const controller = new AbortController()
  const signal = controller.signal

// code ...

  this.getArtist = (id) => {
    return (
      fetch(
        `https://api.spotify.com/v1/artists/${id}`, {
        headers: {"Authorization": "Bearer " + this.user_token}
      }, {signal})
      .then(response => {
        return checkServerStat(response.status, response.json())
      })
    )
  }

  // code ...

  // this is my cancel method
  this.cancelRequest = () => controller.abort()
}

Mon spotify.getArtistProfile() ressemble à ceci

const  ArtistProfile = props => {
  const [artistData, setArtistData] = useState(null)
  const token = props.spotifyAPI.user_token

  const fetchData = () => {
    const id = window.location.pathname.split("/").pop()
    console.log(id)
    props.spotifyAPI.getArtistProfile(id, ["album"], "US", 10)
    .then(data => {setArtistData(data)})
  }
  useEffect(() => {
    fetchData()
    return () => { props.spotifyAPI.cancelRequest() }
  }, [])

  return (
    <ArtistProfileContainer>
      <AlbumContainer>
        {artistData ? artistData.artistAlbums.items.map(album => {
          return (
            <AlbumTag
              image={album.images[0].url}
              name={album.name}
              artists={album.artists}
              key={album.id}
            />
          )
        })
        : null}
      </AlbumContainer>
    </ArtistProfileContainer>
  )
}

mais parce que mon signal est utilisé pour les appels API individuels qui sont résolus dans un Promise.all je ne peux pas abort() cette promesse donc je Promise.all toujours l'état.


3 commentaires

L'avertissement est dû au fait que le getArtistProfile() Promise getArtistProfile() résout une fois le composant démonté. Soit annuler cette demande, ou si ce n'est pas possible ajouter un chèque dans le .then() gestionnaire si setArtistData() n'est pas appelé si le composant a été démonté


Il ne sera pas possible d'expliquer pourquoi cela se produit sans en savoir plus sur votre application en dehors de ce composant. Nous avons besoin de savoir ce qui provoque le montage / démontage de ce composant. Que se passe-t-il dans l'application lorsque vous obtenez l'erreur?


@ ııı Comment pourrais-je vérifier si le composant est démonté?


3 Réponses :


3
votes

Vous pouvez essayer de définir un état comme celui-ci et vérifier si votre composant est monté ou non. De cette façon, vous êtes sûr que si votre composant est démonté, vous n'essayez pas de récupérer quelque chose.

const [didMount, setDidMount] = useState(false); 

useEffect(() => {
   setDidMount(true);
   return () => setDidMount(false);
}, [])

if(!didMount) {
  return null;
}

return (
    <ArtistProfileContainer>
      <AlbumContainer>
        {artistData ? artistData.artistAlbums.items.map(album => {
          return (
            <AlbumTag
              image={album.images[0].url}
              name={album.name}
              artists={album.artists}
              key={album.id}
            />
          )
        })
        : null}
      </AlbumContainer>
    </ArtistProfileContainer>
  )

J'espère que cela vous aidera.


5 commentaires

didMount sera true à l'état non monté.


Pouvez-vous expliquer un peu plus pourquoi?


Le composant se monte, puis l'effet s'exécute et définit didMount sur true , puis le composant se démonte mais didMount n'est jamais réinitialisé


Je pense que vous setDidMount(false) dire setDidMount(false) . Mais le problème serait que la fermeture du gestionnaire .then() aura une référence à un didMount antérieur


C'était une méthode pour résoudre un problème de SSR dans mon application, qui irait également avec ce cas. Sinon, la promesse devrait être annulée, je suppose.



16
votes

Partager le AbortController entre les requêtes fetch() est la bonne approche.
Quand l' une des Promise est annulée, Promise.all() rejettera avec AbortError :

<script src="//cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.development.js"></script>
<main></main>
function Component(props) {
  const [fetched, setFetched] = React.useState(false);
  React.useEffect(() => {
    const ac = new AbortController();
    Promise.all([
      fetch('http://placekitten.com/1000/1000', {signal: ac.signal}),
      fetch('http://placekitten.com/2000/2000', {signal: ac.signal})
    ]).then(() => setFetched(true))
      .catch(ex => console.error(ex));
    return () => ac.abort(); // Abort both fetches on unmount
  }, []);
  return fetched;
}
const main = document.querySelector('main');
ReactDOM.render(React.createElement(Component), main);
setTimeout(() => ReactDOM.unmountComponentAtNode(main), 1); // Unmount after 1ms


0 commentaires

0
votes

Pour moi, nettoyer l'état au démontage du composant aidé.

 const [state, setState] = useState({});

useEffect(() => {
    myFunction();
    return () => {
      setState({}); // This worked for me
    };
}, []);

const myFunction = () => {
    setState({
        name: 'Jhon',
        surname: 'Doe',
    })
}


0 commentaires