Javascript 对未安装的组件执行React-setState()

Javascript 对未安装的组件执行React-setState(),javascript,ajax,reactjs,state,Javascript,Ajax,Reactjs,State,在我的react组件中,当ajax请求正在进行时,我试图实现一个简单的微调器——我使用状态来存储加载状态 出于某种原因,下面我的React组件中的这段代码抛出了这个错误 只能更新已安装或正在安装的组件。这通常意味着 您对未安装的组件调用了setState()。这是禁止的。 请检查未定义组件的代码 如果我去掉第一个setState调用,错误就会消失 构造函数(道具){ 超级(道具); this.loadSearches=this.loadSearches.bind(this); 此.state=

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

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

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

如果我去掉第一个setState调用,错误就会消失

构造函数(道具){
超级(道具);
this.loadSearches=this.loadSearches.bind(this);
此.state={
加载:错误
}
}
加载搜索(){
这是我的国家({
加载:对,
搜索:[]
});
log('加载搜索..');
$.ajax({
url:this.props.source+'?projectId='+this.props.projectId,
数据类型:“json”,
跨域:是的,
成功:功能(数据){
这是我的国家({
加载:错误
});
}.绑定(此),
错误:函数(xhr、状态、错误){
console.error(this.props.url,status,err.toString());
这是我的国家({
加载:错误
});
}.绑定(此)
});
}
componentDidMount(){
setInterval(this.loadSearches、this.props.pollInterval);
}
render(){
让searches=this.state.searches | |[];
返回(
名称
提交日期
数据集和数据类型
结果
上次下载
{
searches.map(函数(搜索){
让createdDate=moment(search.createdDate,'X')。格式(“YYYY-MM-DD”);
让downloadeDate=时刻(search.downloadeDate,'X')。格式(“YYYY-MM-DD”);
设记录=0;
让status=search.status?search.status.toLowerCase():“”
返回(
{search.name}
{createdDate}
{search.dataset}
{记录}
{下载日期}
);
}
);

}
看不到渲染功能有点困难。虽然您已经可以发现应该执行的操作,但每次使用间隔时,您都必须在卸载时清除它。因此:

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

componentWillUnmount () {
    this.loadInterval && clearInterval(this.loadInterval);
    this.loadInterval = false;
}
由于卸载后仍可能调用这些成功和错误回调,因此可以使用interval变量检查是否已装载

this.loadInterval && this.setState({
    loading: false
});
希望这有帮助,如果这不起作用,请提供渲染功能

干杯

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

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

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

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

对于后代

在我们的例子中,此错误与回流、回调、重定向和设置状态有关。我们向onDone回调发送了设置状态,但也向onSuccess回调发送了重定向。如果成功,我们的onSuccess回调在onDone之前执行。这会在尝试设置状态之前导致重定向。因此,在未安装的组件上设置state会导致错误

回流存储操作:

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变量并自己监控它。

对于需要另一个选项的人,ref属性的回调方法可以作为一种解决方法。handleRef的参数是对div-DOM元素的引用

有关参照和DOM的详细信息,请参见:

handleRef=(divElement)=>{
if(divElement){
//在这里设置状态
}
}
render(){
返回(
)
}

共享由启用的解决方案

同样的解决方案也可以扩展到任何时候,只要您想取消以前的请求获取id更改,否则在多个飞行中请求之间就会出现竞争条件(
this.setState
无序调用)

这要感谢javascript中的支持

总的来说,上面的想法与react doc推荐的想法非常接近,react doc明确指出

isMounted是一个反模式

信用

仅供参考。使用装饰器,您可以执行以下操作: ()

导出类TestComponent扩展了React.Component{ 状态={}; @已取消(功能(错误){ warn(`cancelled:${err}`); 如果(错误代码!==E\u原因\u已处理){ this.setState({text:err+“”}); } }) @听 @异步的 *componentDidMount(){ 控制台日志(“已安装”); const json=yield this.fetchJSON( "https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-延迟=2s“ ); this.setState({text:JSON.stringify(JSON)}); } @超时(5000) @异步的 *fetchJSON(url){ const response=yield cpFetch(url);//可取消的请求 返回yield response.json(); } render(){ 返回( AsyncComponent:{this.state.text | |“获取…” ); } @取消(原因已处理) 公司
handleRef = (divElement) => {
 if(divElement){
  //set state here
 }
}

render(){
 return (
  <div ref={this.handleRef}>
  </div>
 )
}
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() {
    ...
  }
}
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)
}, [])
React.useEffect(() => {
  let isCancelled = false

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

  return () => (isCancelled = true)
}, [id])
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");
  }
}