Reacts “Called SetState() on an Unmounted Component”-Fehler beheben

0
170

Aufgerufene setState() auf einer nicht gemounteten Komponente in Ihrer Konsole zu sehen, ist eine von die häufigsten Probleme, mit denen React-Neulinge konfrontiert sind. Wenn Sie mit Klassenkomponenten arbeiten, können Sie leicht Situationen schaffen, in denen dieser Fehler auftritt.

Das Aktualisieren des Status von Komponenten, die aus dem DOM entfernt wurden, signalisiert normalerweise, dass Ihre Anwendung ein Speicherleck aufweist. Dies verschwendet die Hardwareressourcen Ihres Benutzers und führt zu einer allmählichen Leistungsreduzierung, wenn es nicht aktiviert wird. Hier erfahren Sie, warum der Fehler auftritt und was Sie tun können, um ihn zu beheben.

Eine einfache Komponente

Hier ist eine grundlegende React-Komponente, die einige Daten über das Netzwerk abruft:

React aus "react" importieren;   Klasse NewsList erweitert React.Component {   Zustand = {   Nachrichten: null   };     KomponenteDidMount() { holen("http://example.com/news.json").then(res => { this.setState({news: response.json()}); }).catch(e => { Warnung("Fehler!"); }); }     rendern() { wenn (!diese.state.news) return "Laden…"; else return news.map((story, key) => <h1 key={key}>{story.Headline}</h1>); }   }

Diese Komponente birgt die Gefahr, dass bei einer nicht gemounteten Komponente Fehler mit dem Namen setState() generiert werden. Um zu verstehen, warum, bedenken Sie, was passiert, wenn Sie eine Seite besuchen, die die NewsList rendert. Die Komponente stellt eine asynchrone Netzwerkanfrage und fügt dann die abgerufene Nachrichtenliste zu ihrem Status hinzu.

Das Problem tritt auf, wenn die Behebung der Netzwerkanforderung eine Weile dauert. Wenn es viele Neuigkeiten gibt und der Benutzer eine flockige 3G-Verbindung verwendet, kann es möglicherweise mehrere Sekunden dauern. In der Zwischenzeit hat der Benutzer möglicherweise aufgegeben und auf eine andere Seite getippt. Dadurch wird die NewsList ausgehängt und aus dem DOM entfernt.

Werbung

Trotz der Navigation ist die Netzwerkanfrage noch in Bearbeitung. Schließlich wird es aufgelöst und sein .then()-Callback wird ausgeführt. Laut Fehlermeldung wird setState() für die nicht gemountete NewsList-Instanz aufgerufen.

Jetzt verschwenden Sie Speicher, indem Sie die Newsliste im Zustand der nicht gemounteten Komponente speichern. Die Daten werden dem Benutzer niemals angezeigt – Wenn sie zur Seite mit der Nachrichtenliste zurückkehren, wird eine neue NewsList-Komponente gemountet und ihre eigenen Daten abgerufen.

Das Problem lösen

Das Problem mit diesem Beispiel ist leicht zu lösen. Hier ist ein wirklich grundlegender Ansatz:

Klasse NewsList erweitert React.Component {   montiert: falsch;   Zustand = {   Nachrichten: null   };     KomponenteDidMount() {   this.mount = true;   holen("http://example.com/news.json").then(res => { wenn (dieses.montiert) { this.setState({news: response.json()}); } }).catch(e => { Warnung("Fehler!"); });   }     KomponenteWillUnmount() { this.mount = false; }   }

Jetzt werden die Ergebnisse des API-Aufrufs ignoriert, es sei denn, die Komponente ist noch gemountet. Wenn die Komponente ausgehängt werden soll, ruft React componentWillUnmount() auf. Die gemountete Instanzvariable der Komponente wird auf false gesetzt, sodass der Abruf-Callback erkennt, ob er mit dem DOM verbunden ist.

Dies funktioniert, fügt jedoch Boilerplate-Code hinzu, um den gemounteten Zustand zu verfolgen. Der Netzwerkanruf wird ebenfalls vollständig ausgeführt, wodurch möglicherweise Bandbreite verschwendet wird. Hier ist eine bessere Alternative, die einen AbortController verwendet, um den Abrufaufruf mittendrin abzubrechen:

Klasse NewsList erweitert React.Component {   abortController = new AbortController();   Zustand = {   Nachrichten: null   };     KomponenteDidMount() { fetch("http://example.com/news.json", {signal: this.abortController.signal}).then(res => { this.setState({news: response.json()}); }).catch(e => { if (e.name === "AbortError") { //Abbruch beim Aushängen } Sonst Warnung("Fehler!"); }); }     KomponenteWillUnmount() { this.abortController.abort(); }   }

Nun erhält der fetch-Aufruf ein AbortSignal, mit dem die Anfrage abgebrochen werden kann. Wenn React die Komponente aushängen will, wird die Methode abort() des Abort-Controllers aufgerufen. Dies spiegelt sich in dem an den Abruf übergebenen Signal wider, und der Browser verarbeitet das Abbrechen der Netzwerkanforderung. Der .then()-Callback wird nicht ausgeführt, sodass Ihre Komponente nicht versucht, ihren Status nach dem Unmounten zu aktualisieren.

Andere mögliche Ursachen

Eine weitere häufige Ursache für diesen Fehler ist, wenn Sie Ihrer Komponente Ereignis-Listener oder Timer hinzufügen, diese jedoch nicht löschen, wenn die Bereitstellung kurz bevorsteht:

Klasse OfflineWarning erweitert React.Component {   state = {online: navigator.onLine};   handleOnline = () => this.setState({online: true});   handleOffline = () => this.setState({online: false});   KomponenteDidMount() { window.addEventListener("online", this.handleOnline); window.addEventListener("offline", this.handleOffline); }   rendern() { (!this.state.online zurückgeben? "Sie sind offline!" : null); }   } Werbung

Wenn der Nutzer zu einem Bildschirm wechselt, der keine OfflineWarnung rendert, erhalten Sie einen sogenannten setState()-Fehler, wenn sich seine Netzwerkbedingungen ändern. Obwohl die Komponente nicht mehr gemountet ist, sind die von ihr konfigurierten Browser-Ereignis-Listener weiterhin aktiv.

Der Benutzer kann zwischen dem Bildschirm mit OfflineWarning und einem Bildschirm ohne OfflineWarning hin und her wechseln. Dies würde dazu führen, dass es mehrere unsichtbare Komponenteninstanzen gibt, die alle redundant auf Netzwerkereignisse lauschen.

Sie können dies lösen, indem Sie die Vorgänge einfach umkehren, wenn Ihre Komponente ausgehängt wird:

Klasse OfflineWarning erweitert React.Component {   KomponenteDidMount() { window.addEventListener("online", this.handleOnline); window.addEventListener("offline", this.handleOffline); }   KomponenteWillUnmount() { window.removeEventListener("online", this.handleOnline); window.removeEventListener("offline", this.handleOffline); }   }

Verwenden Sie dasselbe Modell, wenn Sie mit Timern und Intervallen arbeiten. Wenn Sie setTimeout() oder setInterval() irgendwo in Ihrer Komponente verwenden, sollten Sie clearTimeout() und clearInterval() vor dem Aushängen verwenden.

Überschreiben von setState() Funktion

Eine andere Möglichkeit besteht darin, eine eigene Basiskomponente zu erstellen, die setState() überschreibt:

Klasse SafeComponent erweitert React.PureComponent {   montiert = falsch;   KomponenteDidMount() { this.mount = true; }   KomponenteWillUnmount() { this.mount = false; }   setState(Zustand, Rückruf) { wenn (dieses.montiert) { super.setState(state, Rückruf); } }   } Werbung

Alle Komponenten, die setState() in einem asynchronen Callback aufrufen, könnten dann von SafeComponent anstelle von React.PureComponent ausgehen. Das übergeordnete SafeComponent-Element verfolgt, ob Ihre Komponente montiert ist. Aufrufe von setState() werden ignoriert, wenn sie empfangen werden, während sie nicht gemountet sind.

Dieser Ansatz behebt nicht wirklich die Wurzel Ihrer Probleme. Es unterdrückt effektiv den Konsolenfehler und vermeidet nicht gemountete Statusaktualisierungen, sollte jedoch nicht anstelle des ordnungsgemäßen Löschens von Timern und Ereignislistenern verwendet werden.

Nichtsdestotrotz kann diese Option als Notlösung hilfreich sein, wenn Sie neu in einer Codebasis mit vielen Problemen sind. Es kann auch eine akzeptable Lösung für Fehler sein, die von kleineren Netzwerkanfragen herrühren, die Sie nicht ordnungsgemäß abbrechen müssen. Wenn Sie mit dem Empfangen und Verwerfen von Daten zufrieden sind, nachdem ein Benutzer einen Bildschirm verlassen hat, können Sie durch die Verwendung einer benutzerdefinierten Basiskomponente das Hinzufügen von wiederholten “ist montiert” Logik zu jeder Ihrer Komponenten.

Wenn Sie diese Route wählen, denken Sie daran, super.componentDidMount() und super.componentWillUnmount() in Ihren untergeordneten Komponenten aufzurufen, wenn Sie diese Methoden überschreiben. Andernfalls wird die bereitgestellte Eigenschaft nicht korrekt festgelegt. Wenn Sie vergessen, super.componentDidMount() aufzurufen, bedeutet das, dass mount immer falsch ist, was dazu führt, dass jede Statusaktualisierung ignoriert wird!

Zusammenfassung

Aufgerufenes setState() auf einem . sehen nicht gemountete Komponente in Ihrer Browserkonsole bedeutet, dass der Callback für einen asynchronen Vorgang noch ausgeführt wird, nachdem eine Komponente aus dem DOM entfernt wurde. Dies weist auf einen Speicherverlust hin, der durch redundante Arbeit verursacht wird, von der der Benutzer nie profitieren wird.

Sie können diese Probleme beheben, indem Sie componentWillUnmount() implementieren und nach Ihrer Komponente ordnungsgemäß bereinigen. Brechen Sie unvollständige Netzwerkanforderungen ab, entfernen Sie Ereignis-Listener und brechen Sie alle von Ihnen erstellten Timer ab. Dadurch wird sichergestellt, dass in Zukunft kein Code mehr ausgeführt werden muss, sodass Ihre Komponente nicht in der Nähe bleiben und versuchen muss, ihren Status zu aktualisieren, nachdem sie das DOM verlassen hat.