【问题标题】:Can't call setState on a component that is not yet mounted无法在尚未安装的组件上调用 setState
【发布时间】:2018-05-03 19:25:09
【问题描述】:

这是我第一次面对这个警告信息。

无法在尚未安装的组件上调用 setState。

关注:

这是一个无操作,但它可能表明您的应用程序中存在错误。而是直接分配给 this.state 或在 MyComponent 组件中定义具有所需状态的 state = {}; 类属性。

尚未安装”部分实际上几乎没有意义,因为触发问题的唯一方法是通过单击组件中的按钮来调用函数需要安装才能看到按钮。该组件在任何给定时间都不会卸载。

这个虚拟组件在我的应用中重现了错误:

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>
    )
  }
}

我正在使用:

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

我在最新的 React/mobx 版本中遗漏了什么吗? (注意该组件不使用任何与 mobx 相关的东西,但它的父级是 mobx-react 观察者)

编辑:

肯定有与组件实例相关的东西,进一步调查表明,在某些情况下,在渲染函数内部创建处理程序会使该警告消失,但并非在所有情况下。

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>
  }
}

编辑 2:

我已经从我的 Webpack 配置中的条目中删除了“react-hot-loader/patch”,并且像这样的一些奇怪问题已经消失。我没有将此作为答案,因为错误消息本身仍然很奇怪,这会导致控制台中出现警告。不过一切正常。

【问题讨论】:

  • 您是否尝试过添加componentWillUnmount 函数并100% 确保您的组件没有被卸载?如果它正在卸载,则问题不在您的组件中。
  • 是的,这就是我所说的“组件在任何给定时间都没有卸载”的意思。
  • 您的代码似乎完全有效,绑定后可能有双括号? bind()()?
  • 我现在没有时间证明,但可能与使用react hot loader有关。我从我的 webpack 配置中的条目中删除了“react-hot-loader/patch”,一些奇怪的问题消失了。

标签: javascript reactjs mobx mobx-react


【解决方案1】:

您收到此警告是因为您在构造函数中设置了对 clickMe 方法 的引用,然后使用 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
   })
  }

尝试从构造函数中删除 this.clickMe = this.clickMe.bind(this),并在 componentWillMount() 或 ComponentDidMount() 等生命周期方法中执行此操作。对于 React 16 及以上版本,您可以使用带有 "SAFE_" 前缀的 componentWillMount 方法[SAFE_componentWillMount]

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

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

【讨论】:

    【解决方案2】:

    只需将以下行添加到您的 代码

    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>
       );
      }
    }
    

    【讨论】:

    • 你能解释一下,他为什么要那样做吗?这条线对我来说似乎有点随机?
    • 因为直到 clickMe() 方法才定义状态“some”
    【解决方案3】:

    您只需要使用componentDidMount() 方法。正如 React 文档在 component life cycle 中所说,如果您需要从远程端点加载数据,这是一个实例化网络请求的好地方,您可以立即在 componentDidMount() 中调用 setState()

    像这样:

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

    【讨论】:

      【解决方案4】:

      正如@Amida 提到的,热加载器似乎是问题所在。谁在使用

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

      在 Startup.cs 中,删除它,问题就会消失。我不知道为什么,但这是我目前的解决方法。

      编辑:

      将“react-hot-loader”和“webpack-hot-middleware”更新到最新版本修复了问题

      【讨论】:

        【解决方案5】:

        您不应在构造函数中使用 setState,因为该组件尚未安装。在这种情况下,clickMe() 方法调用 setState()。
        相反,直接初始化状态。喜欢,

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

        例如来自https://reactjs.org/docs/react-component.html#constructor

        使用 setState() 是为了让我们可以通过重新渲染来反映状态的变化。由于组件尚未渲染,我们可以直接更改状态,更改将反映在 render() 方法的调用上。

        【讨论】:

        • 我的示例代码已经是这样了。无论如何,在某些情况下,问题是由 react-hot-loader 引起的,人们正在转向 react-refresh。
        【解决方案6】:

        如果在您的某个测试中发生此错误,您可能需要在访问之前将组件render 分配给元素(即仅执行let app = new App; 是不够的)。渲染将有效mount the component and its children, as explained in this other answer,然后您将能够使用结果对象执行操作而不会触发setState 错误。一个简单的App.test.js 例子:

        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);
        });
        

        App 组件可能具有的位置:

        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 ( ... ) }
        }
        

        【讨论】:

          【解决方案7】:

          您应该在 constructor() 中分配初始状态。然后立即在 componentDidMount() 中调用 setState()。在浏览器更新屏幕之前触发额外的渲染。在这种情况下,render() 方法将被调用两次,用户不会看到中间状态。

          您的代码将如下所示:

           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>
              )
            }
          }
          
           
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-11-19
            • 2021-07-21
            • 1970-01-01
            相关资源
            最近更新 更多