Technology

#Fixing React’s “Called SetState() on an Unmounted Component” Errors – CloudSavvy IT

“#Fixing React’s “Called SetState() on an Unmounted Component” Errors – CloudSavvy IT”

React logo on a dark background

Seeing Called setState() on an Unmounted Component in your console is one of the most frequent issues faced by newcomers to React. When you’re working with class components, you can easily create situations where you encounter this error.

Updating the state of components that have been removed from the DOM usually signals your application has a memory leak. This wastes your user’s hardware resources and will cause a gradual performance reduction if left unchecked. Here’s why the error occurs and what you can do to resolve it.

A Simple Component

Here’s a basic React component that fetches some data over the network:

import React from "react";
 
class NewsList extends React.Component {
 
    state = {
 
        news: null
 
    };
 
 
    componentDidMount() {
        fetch("http://example.com/news.json").then(res => {
            this.setState({news: response.json()});
        }).catch(e => {
            alert("Error!");
        });
    }
 
 
    render() {
        if (!this.state.news) return "Loading...";
        else return news.map((story, key) => <h1 key={key}>{story.Headline}</h1>);
    }
 
}

This component poses a risk of generating called setState() on an unmounted component errors. To understand why, consider what happens when you visit a page that renders the NewsList. The component will make an asynchronous network request, then add the fetched news list to its state.

The issue occurs when the network request takes a while to resolve. If there’s a lot of news and the user’s on a flaky 3G connection, it could conceivably take several seconds. In the meantime, the user may have given up and tapped to another page. This will unmount NewsList and remove it from the DOM.

Despite the navigation, the network request’s still in-progress. Eventually it will resolve and its .then() callback will run. Per the error message, setState() is called on the unmounted NewsList instance.

Now you’re wasting memory by storing the news list in the unmounted component’s state. The data will never be seen by the user – if they do return to the news list page, a new NewsList component will mount and fetch its own data.

Solving the Problem

The problem with this example is easily solved. Here’s a really basic approach:

class NewsList extends React.Component {
 
    mounted: false;
 
    state = {
 
        news: null
 
    };
 
 
    componentDidMount() {
 
        this.mounted = true;
 
        fetch("http://example.com/news.json").then(res => {
            if (this.mounted) {
                this.setState({news: response.json()});
            }
        }).catch(e => {
            alert("Error!");
        });
 
    }
 
 
    componentWillUnmount() {
        this.mounted = false;
    }
 
}

Now the results of the API call are ignored unless the component’s still mounted. When the component’s about to unmount, React calls componentWillUnmounted(). The component’s mounted instance variable gets set to false, allowing the fetch callback to know whether it’s connected to the DOM.

This works but adds boilerplate code to keep track of the mounted state. The network call will run to completion too, potentially wasting bandwidth. Here’s a better alternative that uses an AbortController to cancel the fetch call midway through:

class NewsList extends React.Component {
 
    abortController = new AbortController();
 
    state = {
 
        news: null
 
    };
 
 
    componentDidMount() {
        fetch("http://example.com/news.json", {signal: this.abortController.signal}).then(res => {
            this.setState({news: response.json()});
        }).catch(e => {
            if (e.name === "AbortError") {
                // Aborted as unmounting
            }
            else alert("Error!");
        });
    }
 
 
    componentWillUnmount() {
        this.abortController.abort();
    }
 
}

Now the fetch call receives an AbortSignal that can be used to cancel the request. When React’s going to unmount the component, the abort controller’s abort() method is called. This will be reflected in the signal passed to fetch and the browser will handle cancellation of the network request. The .then() callback won’t run so your component won’t try to update its state after it unmounts.

Other Possible Causes

Another common cause of this error is when you add event listeners or timers to your component but don’t clear them when it’s about to unmount:

class OfflineWarning extends 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);
    }
 
    render() {
        return (!this.state.online ? "You're offline!" : null);
    }
 
}

If the user moves to a screen that doesn’t render OfflineWarning, you’ll get a called setState() error when their network conditions change. Although the component’s no longer mounted, the browser event listeners it configured will still be active.

The user might move back and forth between the screen with OfflineWarning and one without it several times. This would lead to there being multiple invisible component instances, all redundantly listening for network events.

You can solve this by simply reversing operations when your component’s unmounting:

class OfflineWarning extends React.Component {
 
    componentDidMount() {
        window.addEventListener("online", this.handleOnline);
        window.addEventListener("offline", this.handleOffline);
    }
 
    componentWillUnmount() {
        window.removeEventListener("online", this.handleOnline);
        window.removeEventListener("offline", this.handleOffline);
    }
 
}

Use the same model when working with timers and intervals. If you setTimeout() or setInterval() anywhere in your component, you should clearTimeout() and clearInterval() before it unmounts.

Overriding the setState() Function

Another option is to create your own base component that overrides setState():

class SafeComponent extends React.PureComponent {
 
    mounted = false;
 
    componentDidMount() {
        this.mounted = true;
    }
 
    componentWillUnmount() {
        this.mounted = false;
    }
 
    setState(state, callback) {
        if (this.mounted) {
            super.setState(state, callback);
        }
    }
 
}

Any components which call setState() in an asynchronous callback could then extend from SafeComponent instead of React.PureComponent. The SafeComponent parent keeps track of whether your component’s mounted. Calls to setState() will be ignored if they’re received while unmounted.

This approach isn’t really addressing the root of your problems. It’s effective at suppressing the console error and avoiding unmounted state updates but it shouldn’t be used in lieu of properly clearing timers and event listeners.

Nonetheless this option can be helpful as a stop-gap when you’re new to a codebase with a lot of issues. It may also be an acceptable resolution for errors derived from minor network requests which you don’t feel a need to properly abort. If you’re satisfying with receiving and discarding data after a user browses away from a screen, using a custom base component lets you avoid adding repetitive “is mounted” logic to each of your components.

If you do choose this route, remember to call super.componentDidMount() and super.componentWillUnmount() in your child components when you override those methods. Otherwise the mounted property won’t be set correctly. Forgetting to call super.componentDidMount() will mean mounted is always false, causing every state update to be ignored!

Summary

Seeing called setState() on an unmounted component in your browser console means the callback for an async operation is still running after a component’s removed from the DOM. This points to a memory leak caused by doing redundant work which the user will never benefit from.

You can resolve these issues by implementing componentWillUnmounted() and properly cleaning up after your component. Abort incomplete network requests, remove event listeners, and cancel any timers you’ve created. This will ensure there’s no code left to run in the future so your component won’t need to stick around and try to update its state after it’s gone from the DOM.

If you liked the article, do not forget to share it with your friends. Follow us on Google News too, click on the star and choose us from your favorites.

For forums sites go to Forum.BuradaBiliyorum.Com

If you want to read more like this article, you can visit our Technology category.

Source

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button
Close

Please allow ads on our site

Please consider supporting us by disabling your ad blocker!