【问题标题】:React - setState() on unmounted componentReact - 未安装组件上的 setState()
【发布时间】:2015-12-30 10:01:53
【问题描述】:

在我的反应组件中,我试图在 ajax 请求正在进行时实现一个简单的微调器 - 我使用状态来存储加载状态。

由于某种原因,我的 React 组件下面的这段代码抛出了这个错误

只能更新已安装或正在安装的组件。这通常意味着 您在未安装的组件上调用了 setState() 。这是一个无操作。 请检查未定义组件的代码。

如果我摆脱第一个 setState 调用,错误就会消失。

constructor(props) {
  super(props);
  this.loadSearches = this.loadSearches.bind(this);

  this.state = {
    loading: false
  }
}

loadSearches() {

  this.setState({
    loading: true,
    searches: []
  });

  console.log('Loading Searches..');

  $.ajax({
    url: this.props.source + '?projectId=' + this.props.projectId,
    dataType: 'json',
    crossDomain: true,
    success: function(data) {
      this.setState({
        loading: false
      });
    }.bind(this),
    error: function(xhr, status, err) {
      console.error(this.props.url, status, err.toString());
      this.setState({
        loading: false
      });
    }.bind(this)
  });
}

componentDidMount() {
  setInterval(this.loadSearches, this.props.pollInterval);
}

render() {

    let searches = this.state.searches || [];


    return (<div>
          <Table striped bordered condensed hover>
          <thead>
            <tr>
              <th>Name</th>
              <th>Submit Date</th>
              <th>Dataset &amp; Datatype</th>
              <th>Results</th>
              <th>Last Downloaded</th>
            </tr>
          </thead>
          {
          searches.map(function(search) {

                let createdDate = moment(search.createdDate, 'X').format("YYYY-MM-DD");
                let downloadedDate = moment(search.downloadedDate, 'X').format("YYYY-MM-DD");
                let records = 0;
                let status = search.status ? search.status.toLowerCase() : ''

                return (
                <tbody key={search.id}>
                  <tr>
                    <td>{search.name}</td>
                    <td>{createdDate}</td>
                    <td>{search.dataset}</td>
                    <td>{records}</td>
                    <td>{downloadedDate}</td>
                  </tr>
                </tbody>
              );
          }
          </Table >
          </div>
      );
  }

问题是为什么我应该已经安装组件时出现此错误(因为它是从 componentDidMount 调用的)我认为安装组件后设置状态是安全的?

【问题讨论】:

  • 在我的构造函数中我设置“this.loadSearches = this.loadSearches.bind(this);” - 生病添加到问题
  • 您是否尝试在构造函数中将 loading 设置为 null?那可能行得通。 this.state = { loading : null };
  • 嗨,我知道这是一个非常古老的帖子.. 但只是为了更新最新的发展:setstate 警告已从 React 代码库中删除(参见 PR)。原因是.. 1. 在某些情况下它们是误报 2. 避免误报会导致人们采用不受欢迎的代码模式,可读性较差 3. React 将“提供一个功能,让您保留 DOM 和状态,即使组件是不可见的”,而某些仅仅通过 setState 警告而采用的代码模式可能会导致将来出现不良行为。

标签: javascript ajax reactjs state


【解决方案1】:

没有看到渲染功能有点难。尽管已经可以发现您应该做的事情,但每次使用间隔时,您都必须在卸载时清除它。所以:

componentDidMount() {
    this.loadInterval = setInterval(this.loadSearches, this.props.pollInterval);
}

componentWillUnmount () {
    this.loadInterval && clearInterval(this.loadInterval);
    this.loadInterval = false;
}

由于卸载后可能仍会调用这些成功和错误回调,因此您可以使用间隔变量来检查它是否已安装。

this.loadInterval && this.setState({
    loading: false
});

希望这有帮助,如果这不起作用,请提供渲染功能。

干杯

【讨论】:

  • 布鲁诺,你不能只测试“this”上下文的存在吗.. ala this && this.setState .....
  • 或者简单地说:componentWillUnmount() { clearInterval(this.loadInterval); }
  • @GregHerbowicz 如果您使用计时器卸载和安装组件,即使您进行简单的清除,它仍然可以被触发。
【解决方案2】:

问题是为什么我应该已经安装组件时出现此错误(因为它是从 componentDidMount 调用的)我认为安装组件后设置状态是安全的?

不是componentDidMount调用的。您的componentDidMount 生成一个回调函数,该函数将在计时器处理程序的堆栈中执行,而不是在componentDidMount 的堆栈中执行。显然,当您的回调 (this.loadSearches) 被执行时,组件已被卸载。

因此,接受的答案将保护您。如果您正在使用其他一些不允许您取消异步函数(已经提交给某个处理程序)的异步 API,您可以执行以下操作:

if (this.isMounted())
     this.setState(...

这将消除您在所有情况下报告的错误消息,尽管它确实感觉像是在地毯下扫除东西,特别是如果您的 API 提供取消功能(就像 setIntervalclearInterval 所做的那样)。

【讨论】:

【解决方案3】:

对于需要其他选项的人来说,ref 属性的回调方法可能是一种解决方法。 handleRef 的参数是对 div DOM 元素的引用。

有关 refs 和 DOM 的详细信息:https://facebook.github.io/react/docs/refs-and-the-dom.html

handleRef = (divElement) => {
 if(divElement){
  //set state here
 }
}

render(){
 return (
  <div ref={this.handleRef}>
  </div>
 )
}

【讨论】:

  • 使用 ref 有效地“isMounted”与仅使用 isMounted 完全相同,但不太清楚。 isMounted 不是一个反模式,因为它的名字,而是因为它是一个保存对未安装组件的引用的反模式。
【解决方案4】:
class myClass extends Component {
  _isMounted = false;

  constructor(props) {
    super(props);

    this.state = {
      data: [],
    };
  }

  componentDidMount() {
    this._isMounted = true;
    this._getData();
  }

  componentWillUnmount() {
    this._isMounted = false;
  }

  _getData() {
    axios.get('https://example.com')
      .then(data => {
        if (this._isMounted) {
          this.setState({ data })
        }
      });
  }


  render() {
    ...
  }
}

【讨论】:

  • 有没有办法为功能组件实现这一点? @john_per
  • 对于函数组件我会使用 ref: const _isMounted = useRef(false); @Tamjid
【解决方案5】:

分享由react hooks启用的解决方案。

React.useEffect(() => {
  let isSubscribed = true

  callApi(...)
    .catch(err => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed, ...err }))
    .then(res => isSubscribed ? this.setState(...) : Promise.reject({ isSubscribed }))
    .catch(({ isSubscribed, ...err }) => console.error('request cancelled:', !isSubscribed))

  return () => (isSubscribed = false)
}, [])

可以将相同的解决方案扩展到任何时候取消以前的获取 id 更改请求,否则在多个正在进行的请求之间会出现竞争条件(this.setState 无序调用) .

React.useEffect(() => {
  let isCancelled = false

  callApi(id).then(...).catch(...) // similar to above

  return () => (isCancelled = true)
}, [id])

感谢closures in javascript。

总的来说,上面的想法与react doc推荐的makeCancelable approach很接近,其中明确说明

isMounted 是一种反模式

信用

https://juliangaramendy.dev/use-promise-subscription/

【讨论】:

    【解决方案6】:

    为了后代,

    在我们的例子中,这个错误与 Reflux、回调、重定向和 setState 有关。我们向 onDone 回调发送了一个 setState,但我们也向 onSuccess 回调发送了一个重定向。在成功的情况下,我们的 onSuccess 回调在 onDone 之前执行。这会在尝试的 setState 之前导致重定向。因此错误,setState on an unmounted component。

    回流存储操作:

    generateWorkflow: function(
        workflowTemplate,
        trackingNumber,
        done,
        onSuccess,
        onFail)
    {...
    

    修复前调用:

    Actions.generateWorkflow(
        values.workflowTemplate,
        values.number,
        this.setLoading.bind(this, false),
        this.successRedirect
    );
    

    修复后调用:

    Actions.generateWorkflow(
        values.workflowTemplate,
        values.number,
        null,
        this.successRedirect,
        this.setLoading.bind(this, false)
    );
    

    更多

    在某些情况下,由于 React 的 isMounted 是“已弃用/反模式”,我们采用了 _mounted 变量的使用并自行对其进行监控。

    【讨论】:

      【解决方案7】:

      仅供参考。将 CPromise 与装饰器一起使用,您可以执行以下技巧: (Live demo here)

      export class TestComponent extends React.Component {
        state = {};
      
        @canceled(function (err) {
          console.warn(`Canceled: ${err}`);
          if (err.code !== E_REASON_DISPOSED) {
            this.setState({ text: err + "" });
          }
        })
        @listen
        @async
        *componentDidMount() {
          console.log("mounted");
          const json = yield this.fetchJSON(
            "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s"
          );
          this.setState({ text: JSON.stringify(json) });
        }
      
        @timeout(5000)
        @async
        *fetchJSON(url) {
          const response = yield cpFetch(url); // cancellable request
          return yield response.json();
        }
      
        render() {
          return (
            <div>
              AsyncComponent: <span>{this.state.text || "fetching..."}</span>
            </div>
          );
        }
      
        @cancel(E_REASON_DISPOSED)
        componentWillUnmount() {
          console.log("unmounted");
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2016-10-01
        • 2016-02-22
        • 2018-01-29
        • 1970-01-01
        • 2018-11-15
        • 2019-03-10
        • 2017-12-30
        • 2018-11-19
        • 1970-01-01
        相关资源
        最近更新 更多