Fixar Reacts “Called SetState() på en omonterad komponent”-fel

0
151

Att se Called setState() på en omonterad komponent i din konsol är en av de vanligaste problemen som nykomlingar i React möter. När du arbetar med klasskomponenter kan du enkelt skapa situationer där du stöter på det här felet.

Att uppdatera tillståndet för komponenter som har tagits bort från DOM signalerar vanligtvis att din applikation har en minnesläcka. Detta slösar din användares hårdvaruresurser och kommer att orsaka en gradvis minskning av prestanda om det inte är markerat. Här är varför felet uppstår och vad du kan göra för att lösa det.

En enkel komponent

Här är en grundläggande React-komponent som hämtar en del data över nätverket:

importera Reagera från "reagera";   class NewsList utökar React.Component {   tillstånd = {   nyheter: null   };     componentDidMount() { hämta("http://example.com/news.json").then(res => { this.setState({news: response.json()}); }).catch(e => { varning("Fel!"); }); }     rendera() { om (!this.state.news) returnera "Laster…"; else return news.map((story, key) => <h1 key={key}>{story.Headline}</h1>); }   }

Denna komponent utgör en risk att generera kallade setState() på en omonterad komponentfel. För att förstå varför, fundera över vad som händer när du besöker en sida som återger nyhetslistan. Komponenten kommer att göra en asynkron nätverksbegäran och sedan lägga till den hämtade nyhetslistan till dess status.

Problemet uppstår när nätverksbegäran tar ett tag att lösa. Om det finns många nyheter och användaren är på en fläckig 3G-anslutning kan det tänkas ta flera sekunder. Under tiden kan användaren ha gett upp och klickat till en annan sida. Detta kommer att avmontera NewsList och ta bort den från DOM.

Annons

Trots navigeringen pågår fortfarande nätverksbegäran. Så småningom kommer det att lösas och dess .then() callback kommer att köras. Enligt felmeddelandet anropas setState() på den omonterade NewsList-instansen.

Nu slösar du minne genom att lagra nyhetslistan i den omonterade komponentens tillstånd. Uppgifterna kommer aldrig att ses av användaren – om de återvänder till nyhetslistsidan kommer en ny NewsList-komponent att monteras och hämta sina egna data.

Lösa problemet

Problemet med detta exempel är lätt löst. Här är ett riktigt grundläggande tillvägagångssätt:

class NewsList utökar React.Component {   monterad: falsk;   tillstånd = {   nyheter: null   };     componentDidMount() {   this.mounted = sant;   hämta("http://example.com/news.json").then(res => { om (denna.monterade) { this.setState({news: response.json()}); } }).catch(e => { varning("Fel!"); });   }     componentWillUnmount() { this.mounted = false; }   }

Nu ignoreras resultaten av API-anropet om inte komponenten fortfarande är monterad. När komponenten är på väg att avmonteras, anropar React componentWillUnmounted(). Komponentens monterade instansvariabel ställs in på false, vilket gör att återuppringningen kan veta om den är ansluten till DOM.

Detta fungerar men lägger till boilerplate-kod för att hålla reda på det monterade tillståndet. Nätverkssamtalet kommer också att slutföras, vilket kan slösa bandbredd. Här är ett bättre alternativ som använder en AbortController för att avbryta hämtningsanropet halvvägs:

class NewsList utökar React.Component {   abortController = ny AbortController();   tillstånd = {   nyheter: null   };     componentDidMount() { hämta("http://example.com/news.json", {signal: this.abortController.signal}).then(res => { this.setState({news: response.json()}); }).catch(e => { om (e.name === "AbortError") { //Avbröts som avmontering } annars varning("Fel!"); }); }     componentWillUnmount() { this.abortController.abort(); }   }

Nu får hämtningsanropet en AbortSignal som kan användas för att avbryta begäran. När React ska avmontera komponenten anropas abort-kontrollerns abort()-metod. Detta kommer att återspeglas i signalen som skickas för att hämta och webbläsaren kommer att hantera annulleringen av nätverksbegäran. .then()-återuppringningen kommer inte att köras så din komponent kommer inte att försöka uppdatera sitt tillstånd efter att den har avmonterats.

Andra möjliga orsaker

h2>

En annan vanlig orsak till det här felet är när du lägger till händelseavlyssnare eller timers till din komponent men inte rensar dem när den är på väg att avmonteras:

klass OfflineWarning utökar React.Component {   state = {online: navigator.onLine};   handleOnline = () => this.setState({online: true});   handleOffline = () => this.setState({online: false});   componentDidMount() { window.addEventListener("online", this.handleOnline); window.addEventListener("offline", this.handleOffline); }   rendera() { returnera (!denna.stat.online ? "Du är offline!" : null); }   } Annons

Om användaren flyttar till en skärm som inte visar OfflineWarning, får du ett called setState()-fel när deras nätverksvillkor ändras. Även om komponenten inte längre är monterad, kommer webbläsarens händelseavlyssnare den konfigurerade fortfarande att vara aktiva.

Användaren kan flytta fram och tillbaka mellan skärmen med OfflineWarning och en utan den flera gånger. Detta skulle leda till att det finns flera osynliga komponentinstanser, som alla redundant lyssnar efter nätverkshändelser.

Du kan lösa detta genom att helt enkelt reversera operationer när din komponent avmonteras:

klass OfflineWarning utökar React.Component {   componentDidMount() { window.addEventListener("online", this.handleOnline); window.addEventListener("offline", this.handleOffline); }   componentWillUnmount() { window.removeEventListener("online", this.handleOnline); window.removeEventListener("offline", this.handleOffline); }   }

Använd samma modell när du arbetar med timers och intervaller. Om du setTimeout() eller setInterval() någonstans i din komponent, bör du clearTimeout() och clearInterval() innan den avmonteras.

Åsidosättande av setState() Funktion

Ett annat alternativ är att skapa din egen baskomponent som åsidosätter setState():

klass SafeComponent utökar React.PureComponent {   monterad = falsk;   componentDidMount() { this.mounted = sant; }   componentWillUnmount() { this.mounted = false; }   setState(state, callback) { om (denna.monterade) { super.setState(state, callback); } }   } Annons

Alla komponenter som anropar setState() i en asynkron återuppringning kan sedan sträcka sig från SafeComponent istället för React.PureComponent. SafeComponent-föräldern håller reda på om din komponent är monterad. Anrop till setState() kommer att ignoreras om de tas emot medan de är avmonterade.

Det här tillvägagångssättet åtgärdar inte riktigt roten till dina problem. Det är effektivt för att undertrycka konsolfelet och undvika omonterade tillståndsuppdateringar, men det bör inte användas i stället för att korrekt rensa timers och händelseavlyssnare.

Ändå kan det här alternativet vara användbart som ett stopp när du är ny på en kodbas med många problem. Det kan också vara en acceptabel lösning för fel som härrör från mindre nätverksförfrågningar som du inte känner att du behöver avbryta ordentligt. Om du är nöjd med att ta emot och kassera data efter att en användare har surfat bort från en skärm, kan du med hjälp av en anpassad baskomponent undvika att lägga till repetitiva “är monterad” logik för var och en av dina komponenter.

Om du väljer den här vägen, kom ihåg att anropa super.componentDidMount() och super.componentWillUnmount() i dina underordnade komponenter när du åsidosätter dessa metoder. Annars kommer den monterade egenskapen inte att ställas in korrekt. Att glömma att anropa super.componentDidMount() kommer att innebära att mounted alltid är falskt, vilket gör att varje tillståndsuppdatering ignoreras!

Sammanfattning

Se kallad setState() på en omonterad komponent i din webbläsarkonsol betyder att återuppringningen för en asynkronoperation fortfarande körs efter att en komponent har tagits bort från DOM. Detta pekar på en minnesläcka orsakad av att göra redundant arbete som användaren aldrig kommer att dra nytta av.

Du kan lösa dessa problem genom att implementera componentWillUnmounted() och städa upp ordentligt efter din komponent. Avbryt ofullständiga nätverksförfrågningar, ta bort händelseavlyssnare och avbryt alla timers du har skapat. Detta säkerställer att det inte finns någon kod kvar att köra i framtiden så din komponent behöver inte stanna kvar och försöka uppdatera sitt tillstånd efter att den har försvunnit från DOM.