Reputation: 5442
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 astate = {};
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
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
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
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
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
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
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
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