Javascript Can';t在未安装的组件上执行React状态更新 问题
我正在React中编写一个应用程序,无法避免一个非常常见的陷阱,即在Javascript Can';t在未安装的组件上执行React状态更新 问题,javascript,reactjs,typescript,lodash,setstate,Javascript,Reactjs,Typescript,Lodash,Setstate,我正在React中编写一个应用程序,无法避免一个非常常见的陷阱,即在组件将卸载(…)之后调用设置状态(…) 我非常仔细地查看了我的代码,并试图将一些保护条款放在适当的位置,但问题仍然存在,我仍在遵守警告 因此,我有两个问题: 我如何从堆栈跟踪中找出哪个特定组件和事件处理程序或生命周期钩子对规则冲突负责 好吧,如何解决问题本身,因为我的代码是在考虑这个陷阱的情况下编写的,并且已经在尝试防止它,但是一些底层组件仍然在生成警告 浏览器控制台 警告:无法对未安装的组件执行React状态更新。 这是一个n
组件将卸载(…)
之后调用设置状态(…)
我非常仔细地查看了我的代码,并试图将一些保护条款放在适当的位置,但问题仍然存在,我仍在遵守警告
因此,我有两个问题:
警告:无法对未安装的组件执行React状态更新。
这是一个no-op,但它表示应用程序中存在内存泄漏。
要修复此问题,请取消componentWillUnmount中的所有订阅和异步任务
方法。
在TextLayerInternal中(由Context.Consumer创建)
在TextLayer中(由PageInternal创建)index.js:1446
d/控制台[e]
index.js:1446
不顾一切地警告
react dom.development.js:520
WARNABOUTUPDATEOUNMOUNTED
react dom.development.js:18238
日程安排
react dom.development.js:19684
排队状态
react dom.development.js:12936
./node_modules/react/cjs/react.development.js/Component.prototype.setState
react.development.js:356
_被叫$
TextLayer.js:97
tryCatch
runtime.js:63
援引
runtime.js:282
定义操作方法/无效;
pdfwraper:htmldevelment | null=null;
isComponentMounted:boolean=false;
状态={
隐藏:是的,
pdfWidth:默认宽度,
};
构造器(道具:任何){
超级(道具);
this.setDivSizeThrottleable=节流阀(
() => {
if(this.isComponentMounted){
这是我的国家({
pdfWidth:this.pdfWrapper!.getBoundingClientRect().width-5,
});
}
},
500,
);
}
componentDidMount=()=>{
this.isComponentMounted=true;
this.setDivSizeThrottleable();
window.addEventListener(“resize”,this.setDivSizeThrottleable);
};
组件将卸载=()=>{
this.isComponentMounted=false;
removeEventListener(“resize”,this.setDivSizeThrottleable);
};
渲染=()=>(
{this.state.hidden&&Book正在加载…}
this.pdfWrapper=ref}>
this.onDocumentComplete()}
/>
);
getPdfContentContainerClassName=()=>this.state.hidden?“隐藏的“:”;
onDocumentComplete=()=>{
试一试{
this.setState({hidden:false});
this.setDivSizeThrottleable();
}锁扣(卡紧器错误){
console.warn({caughtError});
}
};
}
导出默认账簿;
AutoWidthPdf.tsx
import { throttle } from 'lodash';
import * as React from 'react';
import { AutoWidthPdf } from '../shared/AutoWidthPdf';
import BookCommandPanel from '../shared/BookCommandPanel';
import BookTextPath from '../static/pdf/sde.pdf';
import './Book.css';
const DEFAULT_WIDTH = 140;
class Book extends React.Component {
setDivSizeThrottleable: () => void;
pdfWrapper: HTMLDivElement | null = null;
isComponentMounted: boolean = false;
state = {
hidden: true,
pdfWidth: DEFAULT_WIDTH,
};
constructor(props: any) {
super(props);
this.setDivSizeThrottleable = throttle(
() => {
if (this.isComponentMounted) {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
}
},
500,
);
}
componentDidMount = () => {
this.isComponentMounted = true;
this.setDivSizeThrottleable();
window.addEventListener("resize", this.setDivSizeThrottleable);
};
componentWillUnmount = () => {
this.isComponentMounted = false;
window.removeEventListener("resize", this.setDivSizeThrottleable);
};
render = () => (
<div className="Book">
{ this.state.hidden && <div className="Book__LoadNotification centered">Book is being loaded...</div> }
<div className={this.getPdfContentContainerClassName()}>
<BookCommandPanel
bookTextPath={BookTextPath}
/>
<div className="Book__PdfContent" ref={ref => this.pdfWrapper = ref}>
<AutoWidthPdf
file={BookTextPath}
width={this.state.pdfWidth}
onLoadSuccess={(_: any) => this.onDocumentComplete()}
/>
</div>
<BookCommandPanel
bookTextPath={BookTextPath}
/>
</div>
</div>
);
getPdfContentContainerClassName = () => this.state.hidden ? 'hidden' : '';
onDocumentComplete = () => {
try {
this.setState({ hidden: false });
this.setDivSizeThrottleable();
} catch (caughtError) {
console.warn({ caughtError });
}
};
}
export default Book;
import * as React from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
interface IProps {
file: string;
width: number;
onLoadSuccess: (pdf: any) => void;
}
export class AutoWidthPdf extends React.Component<IProps> {
render = () => (
<Document
file={this.props.file}
onLoadSuccess={(_: any) => this.props.onLoadSuccess(_)}
>
<Page
pageNumber={1}
width={this.props.width}
/>
</Document>
);
}
import*as React from'React';
从'react pdf'导入{文档,页面,pdfjs};
pdfjs.GlobalWorkerOptions.workerSrc=`//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
接口IProps{
文件:字符串;
宽度:数字;
onLoadSuccess:(pdf:any)=>void;
}
导出类AutoWidthPdf扩展React.Component{
渲染=()=>(
this.props.onLoadSuccess(33;)}
>
);
}
更新1:取消可调节功能(仍然没有运气)
const DEFAULT\u WIDTH=140;
类书扩展了React.Component{
setDivSizeThrottleable:((()=>void)&可取消)|未定义;
pdfwraper:htmldevelment | null=null;
状态={
隐藏:是的,
pdfWidth:默认宽度,
};
componentDidMount=()=>{
this.setDivSizeThrottleable=节流阀(
() => {
这是我的国家({
pdfWidth:this.pdfWrapper!.getBoundingClientRect().width-5,
});
},
500,
);
this.setDivSizeThrottleable();
window.addEventListener(“resize”,this.setDivSizeThrottleable);
};
组件将卸载=()=>{
removeEventListener(“resize”,this.setDivSizeThrottleable!);
此.setDivSizeThrottleable!.cancel();
this.setDivSizeThrottleable=未定义;
};
渲染=()=>(
{this.state.hidden&&Book正在加载…}
this.pdfWrapper=ref}>
this.onDocumentComplete()}
/>
);
getPdfContentContainerClassName=()=>this.state.hidden?“隐藏的“:”;
onDocumentComplete=()=>{
试一试{
this.setState({hidden:false});
这个.setDivSizeThrottleable!();
}锁扣(卡紧器错误){
console.warn({caughtError});
}
};
}
导出默认账簿;
编辑:我刚刚意识到警告引用了一个名为textlayernal
的组件。这可能就是你的错误所在。其余部分仍然相关,但可能无法解决您的问题
1) 获取此警告的组件实例非常困难。在React中似乎有一些讨论来改进这一点,但目前还没有简单的方法。我猜想,它尚未构建的原因可能是,组件的编写方式要求无论组件的状态如何,卸载后的setState都是不可能的。就React团队而言,问题总是在组件代码中,而不是组件实例中,这就是为什么您会得到组件类型名称
这个答案可能不令人满意,但我想我可以解决你的问题
2) 限制的函数有一个
cancel
方法。调用组件中的cancel
将卸载并放弃isComponentMounted
。取消比引入新属性更“惯用”一些。尝试将setDivSizeThrottleable
更改为
this.setDivSizeThrottleable = throttle(
() => {
if (this.isComponentMounted) {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
}
},
500,
{ leading: false, trailing: true }
);
要删除-无法对未安装的组件警告执行反应状态更新,请使用c语言下的componentDidMount方法
const DEFAULT_WIDTH = 140;
class Book extends React.Component {
setDivSizeThrottleable: ((() => void) & Cancelable) | undefined;
pdfWrapper: HTMLDivElement | null = null;
state = {
hidden: true,
pdfWidth: DEFAULT_WIDTH,
};
componentDidMount = () => {
this.setDivSizeThrottleable = throttle(
() => {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
},
500,
);
this.setDivSizeThrottleable();
window.addEventListener("resize", this.setDivSizeThrottleable);
};
componentWillUnmount = () => {
window.removeEventListener("resize", this.setDivSizeThrottleable!);
this.setDivSizeThrottleable!.cancel();
this.setDivSizeThrottleable = undefined;
};
render = () => (
<div className="Book">
{ this.state.hidden && <div className="Book__LoadNotification centered">Book is being loaded...</div> }
<div className={this.getPdfContentContainerClassName()}>
<BookCommandPanel
BookTextPath={BookTextPath}
/>
<div className="Book__PdfContent" ref={ref => this.pdfWrapper = ref}>
<AutoWidthPdf
file={BookTextPath}
width={this.state.pdfWidth}
onLoadSuccess={(_: any) => this.onDocumentComplete()}
/>
</div>
<BookCommandPanel
BookTextPath={BookTextPath}
/>
</div>
</div>
);
getPdfContentContainerClassName = () => this.state.hidden ? 'hidden' : '';
onDocumentComplete = () => {
try {
this.setState({ hidden: false });
this.setDivSizeThrottleable!();
} catch (caughtError) {
console.warn({ caughtError });
}
};
}
export default Book;
this.setDivSizeThrottleable = throttle(
() => {
if (this.isComponentMounted) {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
}
},
500,
{ leading: false, trailing: true }
);
class Home extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
news: [],
};
}
componentDidMount() {
this._isMounted = true;
ajaxVar
.get('https://domain')
.then(result => {
if (this._isMounted) {
this.setState({
news: result.data.hits,
});
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
...
}
}
useEffect(() => {
let isMounted = true; // note mutable flag
someAsyncOperation().then(data => {
if (isMounted) setState(data); // add conditional check
})
return () => { isMounted = false }; // use cleanup to toggle value, if unmounted
}, []); // adjust dependencies to your needs
componentWillUnmount() {
// fix Warning: Can't perform a React state update on an unmounted component
this.setState = (state,callback)=>{
return;
};
}
{!this.props.user.token &&
<div>
<Route path="/register/:type" exact component={MyComp} />
</div>
}
ndex.js:1 Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
import {useHistory} from 'react-router-dom'
const History = useHistory()
if (true) {
history.push('/new-route');
}
return (
<>
<render component />
</>
)
import {Redirect} from 'react-router-dom'
if (true) {
return <redirect to="/new-route" />
}
return (
<>
<render component />
</>
)
import React, { FC, useState, useEffect, DependencyList } from 'react';
export function useEffectAsync( effectAsyncFun : ( isMounted: () => boolean ) => unknown, deps?: DependencyList ) {
useEffect( () => {
let isMounted = true;
const _unused = effectAsyncFun( () => isMounted );
return () => { isMounted = false; };
}, deps );
}
const MyComponent : FC<{}> = (props) => {
const [ asyncProp , setAsyncProp ] = useState( '' ) ;
useEffectAsync( async ( isMounted ) =>
{
const someAsyncProp = await ... ;
if ( isMounted() )
setAsyncProp( someAsyncProp ) ;
});
return <div> ... ;
} ;
let isRendered = useRef(false);
useEffect(() => {
isRendered = true;
axios
.get("/sample/api")
.then(res => {
if (isRendered) {
setState(res.data);
}
return null;
})
.catch(err => console.log(err));
return () => {
isRendered = false;
};
}, []);
import { useRef, useEffect } from 'react';
export function useIsMounted() {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => isMounted.current = false;
}, []);
return isMounted;
}
function Book() {
const isMounted = useIsMounted();
...
useEffect(() => {
asyncOperation().then(data => {
if (isMounted.current) { setState(data); }
})
});
...
}
function asyncRequest(asyncRequest, onSuccess, onError, onComplete) {
let isMounted=true
asyncRequest().then((data => isMounted ? onSuccess(data):null)).catch(onError).finally(onComplete)
return () => {isMounted=false}
}
...
useEffect(()=>{
return asyncRequest(()=>someAsyncTask(arg), response=> {
setSomeState(response)
},onError, onComplete)
},[])
export const useAsync = (
asyncFn,
onSuccess = false,
onError = false,
onFinally = false,
abortFn = false
) => {
useEffect(() => {
let isMounted = true;
const run = async () => {
try{
let data = await asyncFn()
if (isMounted && onSuccess) onSuccess(data)
} catch(error) {
if (isMounted && onError) onSuccess(error)
} finally {
if (isMounted && onFinally) onFinally()
}
}
run()
return () => {
if(abortFn) abortFn()
isMounted = false
};
}, [asyncFn, onSuccess])
}
import React, { useState } from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpFetch from "cp-fetch"; //cancellable c-promise fetch wrapper
export default function TestComponent(props) {
const [text, setText] = useState("");
useAsyncEffect(
function* () {
setText("fetching...");
const response = yield cpFetch(props.url);
const json = yield response.json();
setText(`Success: ${JSON.stringify(json)}`);
},
[props.url]
);
return <div>{text}</div>;
}
import React from "react";
import { ReactComponent, timeout } from "c-promise2";
import cpFetch from "cp-fetch";
@ReactComponent
class TestComponent extends React.Component {
state = {
text: "fetching..."
};
@timeout(5000)
*componentDidMount() {
const response = yield cpFetch(this.props.url);
this.setState({ text: `json: ${yield response.text()}` });
}
render() {
return <div>{this.state.text}</div>;
}
}
const handleClick = async (item: NavheadersType, index: number) => {
const newNavHeaders = [...navheaders];
if (item.url) {
await router.push(item.url); =>>>> line causing error (causing route to happen)
// router.push(item.url); =>>> coreect line
newNavHeaders.forEach((item) => (item.active = false));
newNavHeaders[index].active = true;
setnavheaders([...newNavHeaders]);
}
};
export const SubscriptionsView: React.FC = () => {
const [data, setData] = useState<Subscription[]>();
const isMounted = React.useRef(true);
React.useEffect(() => {
if (isMounted.current) {
// fetch data
// setData (fetch result)
return () => {
isMounted.current = false;
};
}
}
});
useEffect(() => { return () => {}; }, []);
someAsyncOperation().then(data => {
if (isMounted) setState(data); // add conditional check
})
useEffect(() =>
(async () => {
const bar = await fooAsync();
setSomeState(bar);
})(),
[]
);
import { cancelable } from "cancelable-promise";
...
useEffect(
() => {
const cancelablePromise = cancelable(async () => {
const bar = await fooAsync();
setSomeState(bar);
})
return () => cancelablePromise.cancel();
},
[]
);