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