Correction des erreurs “Called SetState() sur un composant non monté” de React

0
200

Voir l'appel setState() sur un composant non monté dans votre console est l'un des problèmes les plus fréquents rencontrés par les nouveaux arrivants réagir. Lorsque vous travaillez avec des composants de classe, vous pouvez facilement créer des situations où vous rencontrez cette erreur.

La mise à jour de l'état des composants qui ont été supprimés du DOM signale généralement que votre application a une fuite de mémoire. Cela gaspille les ressources matérielles de votre utilisateur et entraînera une réduction progressive des performances si rien n'est fait. Voici la raison pour laquelle l'erreur se produit et ce que vous pouvez faire pour la résoudre.

Un composant simple

Voici une base Composant React qui récupère des données sur le réseau :

importer React à partir de “react” ;   la classe NewsList étend React.Component {   état = {   nouvelles : null   };     composantDidMount() { récupérer("http://example.com/news.json").then(res => { this.setState({news: response.json()}); }).catch(e => { alerte("Erreur !”); }); }     rendu() { si (!this.state.news) return "Chargement…"; sinon retourne news.map((histoire, clé) => <touche h1={touche}>{histoire.Titre}</h1>); }   }

Ce composant présente un risque de générer des erreurs appelées setState() sur un composant non monté. Pour comprendre pourquoi, réfléchissez à ce qui se passe lorsque vous visitez une page qui affiche la NewsList. Le composant fera une requête réseau asynchrone, puis ajoutera la liste de nouvelles récupérée à son état.

Le problème se produit lorsque la demande de réseau prend un certain temps à être résolue. S'il y a beaucoup de nouvelles et que l'utilisateur est sur une connexion 3G instable, cela peut prendre plusieurs secondes. En attendant, l'utilisateur peut avoir abandonné et tapé sur une autre page. Cela démontera la NewsList et la supprimera du DOM.

Publicité

Malgré la navigation, la demande de réseau est toujours en cours. Finalement, il se résoudra et son rappel .then() s'exécutera. Selon le message d'erreur, setState() est appelé sur l'instance de NewsList non montée.

Maintenant, vous gaspillez de la mémoire en stockant la liste de nouvelles dans l'état du composant non monté. Les données ne seront jamais vues par l'utilisateur – s'ils retournent à la page de la liste des nouvelles, un nouveau composant NewsList montera et récupérera ses propres données.

Résoudre le problème

Le problème avec cet exemple est facilement résolu. Voici une approche vraiment basique :

la classe NewsList étend React.Component {   monté : faux ;   état = {   nouvelles : null   };     composantDidMount() {   this.mount = vrai;   récupérer("http://example.com/news.json").then(res => { si (ce.monté) { this.setState({news: response.json()}); } }).catch(e => { alerte("Erreur !”); });   }     componentWillUnmount() { this.mount = false; }   }

Maintenant, les résultats de l'appel d'API sont ignorés, sauf si le composant est toujours monté. Lorsque le composant est sur le point d'être démonté, React appelle componentWillUnmount(). La variable d'instance montée du composant est définie sur false, ce qui permet au rappel d'extraction de savoir s'il est connecté au DOM.

Cela fonctionne mais ajoute un code passe-partout pour garder une trace de l'état monté. L'appel réseau se terminera également, ce qui risque de gaspiller de la bande passante. Voici une meilleure alternative qui utilise un AbortController pour annuler l'appel de récupération à mi-chemin :

la classe NewsList étend React.Component {   abortController = new AbortController();   état = {   nouvelles : null   };     composantDidMount() { récupérer("http://example.com/news.json", {signal : this.abortController.signal}).then(res => { this.setState({news: response.json()}); }).catch(e => { si (e.name === "AbortError") { //Abandonné lors du démontage } sinon alerte("Erreur !”); }); }     componentWillUnmount() { this.abortController.abort(); }   }

Maintenant, l'appel fetch reçoit un AbortSignal qui peut être utilisé pour annuler la demande. Lorsque React va démonter le composant, la méthode abort() du contrôleur d'abandon est appelée. Cela sera reflété dans le signal transmis à l'extraction et le navigateur gérera l'annulation de la demande de réseau. Le rappel .then() ne s'exécutera pas, votre composant n'essaiera donc pas de mettre à jour son état après son démontage.

Autres causes possibles

Une autre cause courante de cette erreur est lorsque vous ajoutez des écouteurs d'événement ou des minuteurs à votre composant mais que vous ne les supprimez pas lorsqu'il est sur le point d'être démonté :

la classe OfflineWarning étend React.Component {   état = {en ligne : navigator.onLine};   poignéeEn ligne = () => this.setState({en ligne : vrai});   handleHors ligne = () => this.setState({online: false});   composantDidMount() { window.addEventListener("en ligne", this.handleOnline); window.addEventListener("hors ligne", this.handleHors ligne); }   rendu() { retourner (!ce.état.en ligne ? “Vous êtes hors ligne !” : nul); }   } Publicité

Si l'utilisateur passe à un écran qui n'affiche pas OfflineWarning, vous obtiendrez une erreur setState() appelée lorsque les conditions de son réseau changent. Bien que le composant ne soit plus monté, les écouteurs d'événements du navigateur qu'il a configurés seront toujours actifs.

L'utilisateur peut se déplacer plusieurs fois entre l'écran avec OfflineWarning et l'autre sans. Cela conduirait à plusieurs instances de composants invisibles, toutes écoutant de manière redondante les événements réseau.

Vous pouvez résoudre ce problème en inversant simplement les opérations lors du démontage de votre composant :

la classe OfflineWarning étend React.Component {   composantDidMount() { window.addEventListener("en ligne", this.handleOnline); window.addEventListener("hors ligne", this.handleHors ligne); }   componentWillUnmount() { window.removeEventListener("en ligne", this.handleOnline); window.removeEventListener("hors ligne", this.handleHors ligne); }   }

Utilisez le même modèle lorsque vous travaillez avec des minuteries et des intervalles. Si vous setTimeout() ou setInterval() n'importe où dans votre composant, vous devez clearTimeout() et clearInterval() avant qu'il ne se démonte.

Remplacement de setState() Fonction

Une autre option consiste à créer votre propre composant de base qui remplace setState() :

la classe SafeComponent étend React.PureComponent {   monté = faux ;   composantDidMount() { this.mount = vrai; }   componentWillUnmount() { this.mount = false; }   setState(état, rappel) { si (ce.monté) { super.setState(état, rappel); } }   } Publicité

Tous les composants qui appellent setState() dans un rappel asynchrone pourraient alors s'étendre à partir de SafeComponent au lieu de React.PureComponent. Le parent SafeComponent vérifie si votre composant est monté. Les appels à setState() seront ignorés s'ils sont reçus alors qu'ils sont démontés.

Cette approche n'aborde pas vraiment la racine de vos problèmes. Il est efficace pour supprimer l'erreur de la console et éviter les mises à jour d'état non montées, mais il ne doit pas être utilisé au lieu d'effacer correctement les minuteries et les écouteurs d'événement.

Néanmoins, cette option peut être utile comme palliatif lorsque vous débutez dans une base de code avec beaucoup de problèmes. Cela peut également être une résolution acceptable pour les erreurs dérivées de requêtes réseau mineures que vous ne ressentez pas le besoin d'abandonner correctement. Si vous êtes satisfait de recevoir et de supprimer des données après qu'un utilisateur a quitté un écran, l'utilisation d'un composant de base personnalisé vous permet d'éviter l'ajout répétitif de “est monté” logique à chacun de vos composants.

Si vous choisissez cette route, n'oubliez pas d'appeler super.componentDidMount() et super.componentWillUnmount() dans vos composants enfants lorsque vous remplacez ces méthodes. Sinon, la propriété montée ne sera pas définie correctement. Oublier d'appeler super.componentDidMount() signifiera que mount est toujours faux, ce qui fait que chaque mise à jour d'état est ignorée !

Summary

Voir l'appel setState() sur un composant non monté dans la console de votre navigateur signifie que le rappel pour une opération asynchrone est toujours en cours après la suppression d'un composant du DOM. Cela indique une fuite de mémoire causée par un travail redondant dont l'utilisateur ne bénéficiera jamais.

Vous pouvez résoudre ces problèmes en implémentant componentWillUnmount() et en nettoyant correctement après votre composant. Abandonnez les requêtes réseau incomplètes, supprimez les écouteurs d'événement et annulez tous les minuteurs que vous avez créés. Cela garantira qu'il ne restera plus de code à exécuter à l'avenir, de sorte que votre composant n'aura pas besoin de rester et d'essayer de mettre à jour son état une fois qu'il aura quitté le DOM.