Can't call setState on a component that is not

2020-02-11 18:35发布

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.

3条回答
Melony?
2楼-- · 2020-02-11 18:53

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

查看更多
Emotional °昔
3楼-- · 2020-02-11 18:54

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>
   );
  }
}
查看更多
爷的心禁止访问
4楼-- · 2020-02-11 19:12

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 ( ... ) }
}
查看更多
登录 后发表回答