m_callens
m_callens

Reputation: 6360

Properly unmount React component

Question: Why is this warning being thrown after the component is no longer being rendered by its parent? Am I missing something that needs to be done for unmounting of this component rather than just filtering the store state being passed down the hierarchy of components as props?

I've seen this scenario thrown around a lot, but the solution is usually something that involves unsubscribing the redux store from the component; however, this component is not connected to the store, just top-level container.

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the Feed component.

// @flow
// Feed.js

import React, { Component } from 'react'
import type { FeedType, FeedState } from '../../utils/types'
import { remove, refresh } from '../../actions/redux-actions'
import RssEventList from '../containers/RssEventList'

const cardColors: Array<string> = ['red', 'orange', 'olive', 'green', 'blue', 'yellow']

export default class Feed extends Component {
  props: FeedType
  state: FeedState

  constructor(props: *) {
    super(props)

    this.state = {
      reloading: false
    }
  }

  refresh() {
    this.setState({ reloading: true })
    setInterval(() => this.setState({ reloading: false }), 4000)
    this.props.dispatch(refresh(this.props.link))
  }

  remove() {
    this.props.dispatch(remove(this.props.link))
  }

  render() {
    const color: string = cardColors[Math.floor(Math.random() * cardColors.length)]

    return (
      <div className={`ui ${color} card`}>
        <div className="content">
          <div className="ui header">
            {this.props.title}
            <a className="source link" href={this.props.link} target="_blank">
              <i className="linkify right floated icon"></i>
            </a>
          </div>
          <div className="meta">
            {this.props.description}
          </div>
        </div>
        <div className="content">
          <RssEventList reloading={this.state.reloading} events={this.props.feed} />
        </div>
        <div className="extra content">
          <span className="left floated" onClick={() => this.refresh()}>
            <i className="refresh icon"></i>
            Refresh
          </span>
          <span className="right floated" onClick={() => this.remove()}>
            <i className="cancel icon"></i>
            Remove
          </span>
        </div>
      </div>
    )
  }
}

If it helps, here is a diagram of the component hierarchy:

App (connected to store)
|- Header
|- FilterBar
|- FeedList
   |- Feed
      |- RssEventList
         |- RssEvent
   |- AddCard

Upvotes: 0

Views: 834

Answers (1)

Damien Leroux
Damien Leroux

Reputation: 11693

The problem is that you are not storing your interval on component to remove it when component unmounts. Therefore, the interval will continue to be called even after the component is unmounted. You need to remove it with clearInterval():

export default class Feed extends Component {
  refresh() {
    this.myInterval = setInterval(() => this.setState({ reloading: false }), 4000)
  }

  componentWillUnmount() {
    clearInterval(this.myInterval);
  }
}

Upvotes: 1

Related Questions