3
votes

Comment puis-je vérifier si le composant est démonté dans un composant fonctionnel?

Une fonction de rappel définit l'état du composant. Mais parfois l'abonnement qui fournit les données doit prendre fin. Étant donné que le rappel est exécuté de manière asynchrone, il ne sait pas si l'abonnement se termine juste après l'appel de service (qui exécute la fonction de rappel).

Ensuite, je vois l'erreur suivante dans la console:

Avertissement: impossible d'effectuer une mise à jour de l'état React sur un composant non monté. Il s'agit d'un no-op, mais cela indique une fuite de mémoire dans votre application. Pour résoudre ce problème, annulez tous les abonnements et les tâches asynchrones dans une fonction de nettoyage useEffect.

Existe-t-il un moyen d'accéder à l'état du composant, même si je suis dans la fonction de rappel?

Ce seraient les étapes:

  • s'abonner avec des paramètres
  • Se désabonner
  • le composant est démonté
  • le service abonné exécute la fonction de rappel
  • callback functio définit l'état dans un composant non monté et donne une erreur ci-dessus

1 commentaires

Je suppose que vous utilisez des hooks useState Vous pouvez utiliser useEffect (suggéré dans l'erreur) pour renvoyer une fonction de nettoyage pour muter une variable montée et vérifier cette variable avant d'appeler la fonction useState


5 Réponses :


9
votes

Vous pouvez utiliser une référence comme celle-ci:

const mounted = useRef(false);

useEffect(() => {
    mounted.current = true;

    return () => { mounted.current = false; };
}, []);

Ensuite, dans votre rappel, vous pouvez vérifier si mounted.current === false et éviter de définir l'état


0 commentaires

3
votes

Voici un pseudo-code sur la façon dont vous pouvez utiliser useEffect pour voir si un composant est monté.

Il utilise useEffect pour écouter someService lorsqu'il reçoit un message, il vérifie si le composant est monté (la fonction de nettoyage est également appelée lorsque le composant est démonté) et si c'est le cas, il utilise setServiceMessage qui a été créé par useState pour définir les messages reçus par le service:

import { useState, useEffect } from 'react';
import someService from 'some-service';

export default props => {
  const userId = props.userId;
  const [serviceMessage, setServiceMessage] = useState([]);
  useEffect(
    () => {
      const mounted = { current: true };
      someService.listen(
        //listen to messages for this user
        userId, 
        //callback when message is received
        message => {
          //only set message when component is mounted
          if (mounted.current) {
            setServiceMessage(serviceMessage.concat(message));
          }
      });
      //returning cleanup function
      return () => {
        //do not listen to the service anymore
        someService.stopListen(userId);
        //set mounted to false if userId changed then mounted
        //  will immediately be set to true again and someService
        //  will listen to another user's messages but if the 
        //  component is unmounted then mounted.current will 
        //  continue to be false
        mounted.current = false;
      };
    },//<-- the function passed to useEffects
    //the function passed to useEffect will be called
    //every time props.userId changes, you can pass multiple
    //values here like [userId,otherValue] and then the function
    //will be called whenever one of the values changes
    //note that when this is an object then {hi:1} is not {hi:1}
    //referential equality is checked so create this with memoization
    //if this is an object created by mapStateToProps or a function
    [userId]
  );
};


0 commentaires

0
votes

J'ai trouvé la réponse acceptée à cette question difficile à lire, et React fournit sa propre documentation sur cette question . Leur exemple est:

const Fade: React.FC<{}> = ({ children }) => {
  const [ className, setClassName ] = useState('fade')
  const [ newChildren, setNewChildren ] = useState(children)

  useEffect(() => {
    setClassName('fade')

    const timerId = setTimeout(() => {
      setClassName('fade show')
      setNewChildren(children)
    }, TIMEOUT_DURATION)

    return () => {
      clearTimeout(timerId)
    }   

  }, [children])

  return <Container fluid className={className + ' p-0'}>{newChildren}</Container>
}

J'ai créé un composant que j'appelle <Fade> qui fera apparaître / disparaître tous les enfants donnés. Notez qu'il repose sur les classes .fade et .show , bien que celles-ci puissent facilement être implémentées sans bootstrap.

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

Tout se résume à une règle: vous désabonner de vos tâches asynchrones dans la fonction de nettoyage renvoyée par useEffect .


0 commentaires

0
votes

Vous pouvez renvoyer une fonction de useEffect , qui sera déclenchée lors du démontage d' un composant fonctionnel. Lis ça s'il te plait

import React, { useEffect } from 'react';
const ComponentExample = () => {
    useEffect(() => {
        // Anything in here is fired on component mount.
        return () => {
            // Anything in here is fired on component unmount.
        }
    }, [])
}


0 commentaires

0
votes

Ce crochet (inspiré de la réponse de Mohamed) résout le problème d'une manière plus élégante:

function useMounted() {
  const mounted = useRef(true);
  useEffect(() => {
    return () => { mounted.current = false}
  }, [mounted]);
  return mounted;
}


0 commentaires