Cancel All Subscriptions and Asyncs in the componentWillUnmount Method, how?

Dres picture Dres · Aug 28, 2018 · Viewed 45.5k times · Source

I'm getting an error message due to an async method issue. In my terminal I'm seeing:

Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
- node_modules/fbjs/lib/warning.js:33:20 in printWarning
- node_modules/fbjs/lib/warning.js:57:25 in warning
- node_modules/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js:12196:6 in warnAboutUpdateOnUnmounted
- node_modules/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js:13273:41 in scheduleWorkImpl
- node_modules/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js:6224:19 in enqueueSetState
- node_modules/react/cjs/react.development.js:242:31 in setState
* router/_components/item.js:51:16 in getImage$
- node_modules/regenerator-runtime/runtime.js:62:44 in tryCatch
- node_modules/regenerator-runtime/runtime.js:296:30 in invoke
- ... 13 more stack frames from framework internals

I noticed it's specifically pointing out the getImage$

Here's the code I'm using for that section:

export default class extends Component {
    constructor(props) {
        super(props);
        const { item } = props

        const bindThese = { item }
        this.boundActionCreators = bindActionCreators(bindThese)

        this.state = {
            image: require('../../static/logo.png'),
            ready: false,
            showOptions: this.props.showOptions
        }

        this.getImage = this.getImage.bind(this)
        this.renderNotAdmin = this.renderNotAdmin.bind(this)
        this.renderAdmin = this.renderAdmin.bind(this)
        this.handleOutOfStock = this.handleOutOfStock.bind(this)
    }

    async getImage(img) {
        let imgUri = await Amplify.Storage.get(img)
        let uri = await CacheManager.get(imgUri).getPath()

        this.setState({
            image: { uri },
            ready: true
        })
    }

    componentDidMount() {
        this.getImage(this.props.item.image)
    }

I'm trying to figure out how to use a componentWillUnmount with this async method. How do I go about it?

Thanks!

Answer

Sakhi Mansoor picture Sakhi Mansoor · Aug 28, 2018

You can use isMounted React pattern to avoid memory leaks here.

In your constructor:

constructor(props) {
    super(props);

    this._isMounted = false;
// rest of your code
}

componentDidMount() {
    this._isMounted = true;
    this._isMounted && this.getImage(this.props.item.image);

}

in your componentWillUnmount

componentWillUnmount() {
   this._isMounted = false;
}

While in you getImage()

async getImage(img) {
    let imgUri = await Amplify.Storage.get(img)
    let uri = await CacheManager.get(imgUri).getPath()

    this._isMounted && this.setState({
        image: { uri },
        ready: true
    })
}

A recommend approach to use Axios which is based cancellable promise pattern. So you can cancel any network call while unmounting the component with it's cancelToken subscription. Here is resource for Axios Cancellation