Kev
Kev

Reputation: 5442

Can't call setState on a component that is not yet mounted

this is the first time I face this warning message.

Can't call setState on a component that is not yet mounted.

Follows:

This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the MyComponent component.

The "not yet mounted" part actually makes little to no sense as the only way to trigger the issue is to call a function by clicking a button from a component that needs to be mounted in order to see the button. The component is not unmounted at any given time neither.

This dummy component reproduces the error in my app:

import PropTypes from 'prop-types'
import React from 'react'

export default class MyComponent extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      initial: 'state'
    }
    this.clickMe = this.clickMe.bind(this)
  }

  clickMe () {
    this.setState({
      some: 'new state'
    })
  }

  render () {
    return (
      <div>
        <button onClick={this.clickMe}>click</button>
      </div>
    )
  }
}

I am using:

"react": "16.3.2",
"react-dom": "16.3.2",
"mobx": "4.2.0",
"mobx-react": "5.1.2",

Did I miss something in the latest React/mobx version? (note the component does not use any mobx related stuff but its parent is a mobx-react observer)

Edit:

There must be something related to the component instance, further investigation has shown that in some cases, creating an handler inside the render function will make this warning disappear, but not in all cases.

class MyComponent extends React.component {
  constructor (props) {
    // ...
    this.clickMeBound = this.clickMe.bind(this)
  }

  clickMe () {
    ...
  }

  render () {
    // works
    <button onClick={() => {this.clickMe()}}>click arrow in render</button>

    // warning: Can't call setState on a component that is not yet mounted.
    <button onClick={this.clickMeBound}>click bound</button>
  }
}

Edit 2:

I have removed 'react-hot-loader/patch' from my entries in my Webpack config and some weird issues like this one have disappeared. I'm not putting this as an answer because the error message itself is still weird and this causes a warning in the console. Everything works fine though.

Upvotes: 54

Views: 89274

Answers (7)

monim
monim

Reputation: 4383

You should assign the initial state in the constructor() . then call setState() immediately in componentDidMount(). to trigger an extra rendering, before the browser updates the screen. in this case render() method will be called twice and the user won’t see the intermediate state.

Your code will look like this :

 import PropTypes from 'prop-types'
import React from 'react'

export default class MyComponent extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      initial: 'state'  // initialize your state in the constructor()
    }    
  }
  componentDidMount(){
  this.clickMe = this.clickMe.bind(this)
  clickMe () {   // call setState() in componentDidMount()
    this.setState({
      some: 'new state'
    })
  }
}

  render () {
    return (
      <div>
        <button onClick={this.clickMe}>click</button>
      </div>
    )
  }
}

 

Upvotes: 0

Varshil Patel
Varshil Patel

Reputation: 161

You should not use setState in the constructor since the component is not mounted yet. In this case clickMe() method calls setState().
Instead, initialize the state directly. Like,

constructor(props) {
    super(props);
    // Don't call this.setState() here!
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
}

example if from https://reactjs.org/docs/react-component.html#constructor

The setState() is used so that we can reflect the state changes by re-rendering. Since the component is not rendered yet we can change the state directly and changes will reflect on the call of the render() method.

Upvotes: 7

Anuj Shukla
Anuj Shukla

Reputation: 239

This warning that you are getting is because you are setting a reference to clickMe method in the constructor, which is then using the setState().

constructor (props) {
    super(props)
    this.state = {
       initial: 'state',
       some: ''          
    }
   this.clickMe = this.clickMe.bind(this);   <--- This method
  }

 clickMe () {
   this.setState({
     some: 'new state'    <-- the setState reference that is causing the issue
   })
  }

Try removing the this.clickMe = this.clickMe.bind(this) from constructor and do it in a lifecycle method like componentWillMount() or ComponentDidMount(). For react 16 and above you can use the componentWillMount method with "SAFE_" prefix. [SAFE_componentWillMount]

 componentWillMount() {
      this.clickMe = this.clickMe.bind(this);
 }

clickMe () {
   this.setState({
     some: 'new state'    
   })
  }

Upvotes: 23

Dagim Belayneh
Dagim Belayneh

Reputation: 141

You just need to use the componentDidMount() method. As the React documentation tells in the component life cycle, if you need to load data from a remote endpoint, this is a good place to instantiate the network request and you may call setState() immediately in componentDidMount().

Like this:

componentDidMount(){
  this.clickMe = this.clickMe.bind(this);
}
clickMe () {
  this.setState({
    some: 'new state'
  })
}

Upvotes: 9

Mahdi Bashirpour
Mahdi Bashirpour

Reputation: 18803

Just add following line to your code

export default class MyComponent extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
       initial: 'state',
       some: ''          // <-------  THIS LINE
    }
   this.clickMe = this.clickMe.bind(this)
  }

 clickMe () {
   this.setState({
     some: 'new state'
   })
  }

 render () {
   return (
     <div>
       <button onClick={this.clickMe}>click</button>
     </div>
   );
  }
}

Upvotes: 8

CPHPython
CPHPython

Reputation: 13719

If this error is happening in one of your tests, you might need to render the component to an element before accessing it (i.e. simply doing let app = new App; is not enough). Rendering will effectively mount the component and its children, as explained in this other answer and then you will be able to use the result object to perform operations without triggering the setState error. A simple App.test.js example:

import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div); // <-- App component mounted here
  // without JSX: ReactDOM.render(React.createElement(App), div)
  ReactDOM.unmountComponentAtNode(div);
});

test('array length decreased after removal', () => {
  const div = document.createElement('div');
  let app = ReactDOM.render(<App />, div); // <-- App component mounted here
  const origArrLen = app.state.arr.length;

  app.removeAtIndex(0);
  expect(app.state.arr.length).toEqual(origArrLen - 1);
  ReactDOM.unmountComponentAtNode(div);
});

Where the App component could have:

class App extends Component {
  state = {
    arr: [1,2,3]
  };

  removeAtIndex = index => {
    const { arr } = this.state;
    this.setState({ arr: arr.filter((el, i) => i !== index) });
  };
  // render() { return ( ... ) }
}

Upvotes: 1

Antoan Elenkov
Antoan Elenkov

Reputation: 781

As @Amida mentioned, hot-loader seems to be the issue. Whoever is using

app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
    HotModuleReplacement = true,
    ReactHotModuleReplacement = true
});

in Startup.cs, remove it and the issue will disappear. I don't know why, but this is my current workaround.

EDIT:

Update of "react-hot-loader" and "webpack-hot-middleware" to latest versions fixed the issue

Upvotes: 3

Related Questions