41

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.

Kev
  • 4,017
  • 4
  • 26
  • 47
  • Have you tried to add a `componentWillUnmount` function and make 100% sure your component isn't getting unmounted? If it's getting unmounted, then the problem is not in your component. – Ryan Wheale May 03 '18 at 19:29
  • Yes, that's what I meant by "The component is not unmounted at any given time neither." – Kev May 03 '18 at 19:34
  • Your code seems perfectly valid maybe have a double parenthesis after bind? `bind()()`? – Joel Harkes May 03 '18 at 20:26
  • 1
    I don't have time to prove it right now, but it could be related to the use of react hot loader. I have removed 'react-hot-loader/patch' from my entries in my webpack config and some weird issues disappeared. – Kev Jun 19 '18 at 22:22

6 Answers6

14

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'    
   })
  }
Anuj Shukla
  • 141
  • 1
  • 4
7

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>
   );
  }
}
Mahdi Bashirpour
  • 9,916
  • 5
  • 70
  • 101
4

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'
  })
}
Azametzin
  • 4,342
  • 12
  • 22
  • 38
Dagim Belayneh
  • 91
  • 1
  • 1
  • 6
4

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.

  • 2
    My example code is already like that. Anyway the issue is caused by react-hot-loader in some cases and people are moving to react-refresh. – Kev Oct 08 '20 at 18:33
2

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

Antoan Elenkov
  • 556
  • 1
  • 4
  • 13
1

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 ( ... ) }
}
CPHPython
  • 6,842
  • 2
  • 41
  • 58