erichardson30
erichardson30

Reputation: 5054

React setState can only update a mounted or mounting component

I am getting the following warning

"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 ContactPage component."

When I initially go to the contact page the firs time it is fine. Then if I navigate off the page and go back the warning is thrown.

Contact page component :

import React, { Component, PropTypes } from 'react';
import AppStore from '../../stores/AppStore';
import AppActions from '../../actions/AppActions';
import DataContent from './DataContent';

const title = 'Contact Us';


class ContactPage extends Component {

    constructor(props) {
        super(props);
        this.state = AppStore.getState();
        AppActions.getData();
    }

  static contextTypes = {
    onSetTitle: PropTypes.func.isRequired,
  };

  componentWillMount() {
    this.context.onSetTitle(title);
    AppStore.listen(this.onChange.bind(this));
}

componentWillUnmount() {
    AppStore.unlisten(this.onChange.bind(this));
}

onChange(state) {
    this.setState(state);
}

renderData() {
    return this.state.data.map((data) => {
        return (
            <DataContent key={data.id} data={data} />
        )
    })
}

  render() {
    return (
      <div className={s.root}>
        <div className={s.container}>
          <h1>{title}</h1>
          <div>
              { this.renderData() }
          </div>
        </div>
      </div>
    );
}

}

export default ContactPage;

When I put debuggers in, on load of contact page it hits componentWillMount(). When I leave the contact page it hits componentWillUnmount(). When I navigate back to the page it hits componentWillMount() again and then throws the error when it hits the onChange(state) function.

Upvotes: 22

Views: 34734

Answers (4)

Yuri Nalin
Yuri Nalin

Reputation: 1

I was struggling with this issue as well for quite a while.

What solved for me was to do as @Felix Kling proposed but also make sure my callback function was declared within the component's class, otherwise you're not able to use the this prefix, wich was apparently what did the trick for me.

Here is an example of what I mean:

Legal, but doesn't work

function onChange() { 
  this.setState({ MyState: newState })
}

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    onChange = onChange.bind(this);
  }

  componentDidMount() {
    window.addEventListener('resize', onChange);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', onChange);
  }

  render() { ... }
}

Works

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.onChange = this.onChange.bind(this);
  }

  onChange() { 
    this.setState({ MyState: newState })
  }

  componentDidMount() {
    window.addEventListener('resize', this.onChange);
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.onChange);
  }

  render() { ... }
}

Hope it helps anyone else facing this problem. :)

Upvotes: -1

rEjITh
rEjITh

Reputation: 79

In your case add a ref to your root div and check the ref value before you call setState.

Your onChange method should looks like this:

onChange(state) {
    this.refs.yourRef ? this.setState(state) : null;    
}

Upvotes: 0

Janaka Pushpakumara
Janaka Pushpakumara

Reputation: 5107

Before the change, state check component is mounted.

render() {
   return (
     <div ref="root" className={s.root}>
       // ----
     </div>
   )};

 onChange(state) {
   if(this.refs.root) {
     this.setState(state);
   }
 }

I think this will help to you.

Upvotes: 6

Felix Kling
Felix Kling

Reputation: 816462

The issue is that the listener of the previous component instance is still registered. And because the previous instance isn't mounted anymore, you get that error.

.bind always returns a new function. So if you do

AppStore.unlisten(this.onChange.bind(this));

then you are trying to remove a listener that doesn't exist (which fails of course). It does not remove the listener you registered with AppStore.listen(this.onChange.bind(this))


To solve this, you should bind the handler once in the constructor:

this.onChange = this.onChange.bind(this);

and then use AppStore.listen(this.onChange) and AppStore.unlisten(this.onChange).

Upvotes: 38

Related Questions