Javascript 反应 - 在未安装的组件上设置状态()
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32903001/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
React - setState() on unmounted component
提问by Marty
In my react component im trying to implement a simple spinner while an ajax request is in progress - im using state to store the loading status.
在我的反应组件中,我试图在 ajax 请求正在进行时实现一个简单的微调器 - 我使用状态来存储加载状态。
For some reason this piece of code below in my React component throws this error
出于某种原因,我的 React 组件下面的这段代码抛出了这个错误
Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component.
只能更新已安装或正在安装的组件。这通常意味着您在未安装的组件上调用了 setState()。这是一个无操作。请检查未定义组件的代码。
If I get rid of the first setState call the error goes away.
如果我摆脱了第一个 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 & 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>
);
}
The question is why am I getting this error when the component should already be mounted (as its being called from componentDidMount) I thought it was safe to set state once the component is mounted ?
问题是为什么当组件应该已经安装时我会收到这个错误(因为它是从 componentDidMount 调用的)我认为一旦安装了组件就可以安全地设置状态?
采纳答案by Bruno Mota
Without seeing the render function is a bit tough. Although can already spot something you should do, every time you use an interval you got to clear it on unmount. So:
没有看到渲染功能有点困难。虽然已经可以发现您应该做的事情,但每次使用间隔时,您都必须在卸载时清除它。所以:
componentDidMount() {
this.loadInterval = setInterval(this.loadSearches, this.props.pollInterval);
}
componentWillUnmount () {
this.loadInterval && clearInterval(this.loadInterval);
this.loadInterval = false;
}
Since those success and error callbacks might still get called after unmount, you can use the interval variable to check if it's mounted.
由于卸载后可能仍会调用那些成功和错误回调,因此您可以使用间隔变量来检查它是否已安装。
this.loadInterval && this.setState({
loading: false
});
Hope this helps, provide the render function if this doesn't do the job.
希望这会有所帮助,如果这不起作用,请提供渲染功能。
Cheers
干杯
回答by Marcus Junius Brutus
The question is why am I getting this error when the component should already be mounted (as its being called from componentDidMount) I thought it was safe to set state once the component is mounted ?
问题是为什么当组件应该已经安装时我会收到这个错误(因为它是从 componentDidMount 调用的)我认为一旦安装了组件就可以安全地设置状态?
It is notcalled from componentDidMount
. Your componentDidMount
spawns a callback function that will be executed in the stack of the timer handler, not in the stack of componentDidMount
. Apparently, by the time your callback (this.loadSearches
) gets executed the component has unmounted.
它不是从 调用的componentDidMount
。您会componentDidMount
生成一个回调函数,该函数将在计时器处理程序的堆栈中执行,而不是在componentDidMount
. 显然,当您的回调 ( this.loadSearches
) 执行时,组件已卸载。
So the accepted answer will protect you. If you are using some other asynchronous API that doesn't allow you to cancel asynchronous functions (already submitted to some handler) you could do the following:
因此,接受的答案将保护您。如果您使用的其他异步 API 不允许您取消异步函数(已提交给某个处理程序),您可以执行以下操作:
if (this.isMounted())
this.setState(...
This will get rid of the error message you report in all cases though it does feel like sweeping stuff under the rug, particularly if your API provides a cancel capability (as setInterval
does with clearInterval
).
这将消除您在所有情况下报告的错误消息,尽管它确实感觉像是在掩饰下的东西,特别是如果您的 API 提供取消功能(与setInterval
一样clearInterval
)。
回答by burakhan alkan
To whom needs another option, the ref attribute's callback method can be a workaround. The parameter of handleRef is the reference to div DOM element.
对于需要另一种选择的人,ref 属性的回调方法可以是一种解决方法。handleRef 的参数是对 div DOM 元素的引用。
For detailed information about refs and DOM: https://facebook.github.io/react/docs/refs-and-the-dom.html
有关 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>
)
}
回答by john_per
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() {
...
}
}
回答by Geoffrey Hale
For posterity,
为后人,
This error, in our case, was related to Reflux, callbacks, redirects and setState. We sent a setState to an onDone callback, but we also sent a redirect to the onSuccess callback. In the case of success, our onSuccess callback executes before the onDone. This causes a redirect before the attempted setState. Thus the error, setState on an unmounted component.
在我们的例子中,这个错误与回流、回调、重定向和 setState 相关。我们向 onDone 回调发送了一个 setState,但我们也向 onSuccess 回调发送了一个重定向。在成功的情况下,我们的 onSuccess 回调在 onDone 之前执行。这会在尝试 setState 之前导致重定向。因此,未安装组件上的错误 setState 。
Reflux store action:
回流存储动作:
generateWorkflow: function(
workflowTemplate,
trackingNumber,
done,
onSuccess,
onFail)
{...
Call before fix:
修复前调用:
Actions.generateWorkflow(
values.workflowTemplate,
values.number,
this.setLoading.bind(this, false),
this.successRedirect
);
Call after fix:
修复后调用:
Actions.generateWorkflow(
values.workflowTemplate,
values.number,
null,
this.successRedirect,
this.setLoading.bind(this, false)
);
More
更多的
In some cases, since React's isMounted is "deprecated/anti-pattern", we've adopted the use of a _mounted variable and monitoring it ourselves.
在某些情况下,由于 React 的 isMounted 是“弃用/反模式”,我们采用了 _mounted 变量的使用并自己监控它。
回答by Xlee
Share a solution enabled by react hooks.
分享一个由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)
}, [])
the same solution can be extended to whenever you want to cancelprevious requests on fetch id changes, otherwise there would be race conditions among multiple in-flight requests (this.setState
called out of order).
可以将相同的解决方案扩展到任何时候您想取消之前对 fetch id 更改的请求,否则多个进行中的请求之间会出现竞争条件(this.setState
称为乱序)。
React.useEffect(() => {
let isCancelled = false
callApi(id).then(...).catch(...) // similar to above
return () => (isCancelled = true)
}, [id])
this works thanks to closuresin javascript.
这要归功于javascript 中的闭包。
In general, the idea above was close to the makeCancelable approachrecommended by the react doc, which clearly states
总的来说,上面的想法接近于react doc 推荐的makeCancelable 方法,它明确指出
isMounted is an Antipattern
isMounted 是一个反模式