不能对未挂载的组件执行React状态更新

问题

我正在用React编写一个应用程序,无法避免一个超级常见的陷阱,即在componentWillUnmount(...)之后调用setState(...)

我非常仔细地查看了我的代码,并试图在适当的位置放置一些保护子句,但问题仍然存在,我仍然观察到警告。

因此,我有两个问题:

  1. 如何从堆栈跟踪中找出答案,哪个特定的组件和事件处理程序或生命周期钩子负责违反规则?
  2. 好吧,如何修复问题本身,因为我的代码在编写时就考虑到了这个陷阱,并且已经试图防止它,但一些底层组件仍然生成警告。

浏览器控制台

Warning: Can't perform a React state update on an unmounted component.
This is a no-op, but it indicates a memory leak in your application.
To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount
method.
in TextLayerInternal (created by Context.Consumer)
in TextLayer (created by PageInternal) index.js:1446
d/console[e]
index.js:1446
warningWithoutStack
react-dom.development.js:520
warnAboutUpdateOnUnmounted
react-dom.development.js:18238
scheduleWork
react-dom.development.js:19684
enqueueSetState
react-dom.development.js:12936
./node_modules/react/cjs/react.development.js/Component.prototype.setState
react.development.js:356
_callee$
TextLayer.js:97
tryCatch
runtime.js:63
invoke
runtime.js:282
defineIteratorMethods/</prototype[method]
runtime.js:116
asyncGeneratorStep
asyncToGenerator.js:3
_throw
asyncToGenerator.js:29

enter image description here

代码

Book.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;

AutoWidthPdf.tsx

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>
);
}

更新1:取消可节流功能(仍然没有运气)

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;
648093 次浏览

编辑:我刚刚意识到警告引用了一个名为TextLayerInternal的组件。这可能就是你的bug所在。其余的内容仍然是相关的,但它可能无法解决您的问题。

1)获取这个警告的组件实例很困难。看起来在React中有一些改进的讨论,但目前没有简单的方法来做到这一点。我怀疑,它还没有构建的原因很可能是因为组件被期望以这样一种方式编写,即不管组件的状态是什么,卸载后的setState都不可能。就React团队而言,问题总是在组件代码中,而不是在组件实例中,这就是为什么你得到组件类型名称的原因。

这个答案可能不令人满意,但我想我可以解决你的问题。

2) lodash节流函数有一个cancel方法。在componentWillUnmount中调用cancel并丢弃isComponentMounted。取消比引入新属性更“习惯”。

尝试将setDivSizeThrottleable改为

this.setDivSizeThrottleable = throttle(
() => {
if (this.isComponentMounted) {
this.setState({
pdfWidth: this.pdfWrapper!.getBoundingClientRect().width - 5,
});
}
},
500,
{ leading: false, trailing: true }
);

移除-不能对卸载的组件警告执行React状态更新,在条件下使用componentDidMount方法,并在componentWillUnmount方法上使该条件为假。例如:-

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() {
...
}
}

我有这个警告可能是因为从一个效果钩子调用setState(这将在这3个问题 有关 在一起中讨论)。

不管怎样,升级react版本删除了警告。

我知道你没有使用历史记录,但在我的情况下,我使用了React Router DOM中的useHistory钩子,它在状态被持久化到React Context Provider之前卸载组件。

为了解决这个问题,我使用了钩子withRouter嵌套组件,在我的例子中是export default withRouter(Login),在组件中是const Login = props => { ...; props.history.push("/dashboard"); ...。我还从组件中删除了另一个props.history.push,例如if(authorization.token) return props.history.push('/dashboard'),因为这会导致循环,因为authorization状态。

将新项推入历史的替代方法。

下面是一个反应钩子特定的解决方案

错误

警告:无法对未挂载的组件执行React状态更新。

解决方案

你可以在useEffect中声明let isMounted = true,一旦组件被卸载,它就会在清理回调中被改变。在状态更新之前,你现在有条件地检查这个变量:

useEffect(() => {
let isMounted = true;               // note mutable flag
someAsyncOperation().then(data => {
if (isMounted) setState(data);    // add conditional check
})
return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []);                               // adjust dependencies to your needs

const Parent = () => {
const [mounted, setMounted] = useState(true);
return (
<div>
Parent:
<button onClick={() => setMounted(!mounted)}>
{mounted ? "Unmount" : "Mount"} Child
</button>
{mounted && <Child />}
<p>
Unmount Child, while it is still loading. It won't set state later on,
so no error is triggered.
</p>
</div>
);
};


const Child = () => {
const [state, setState] = useState("loading (4 sec)...");
useEffect(() => {
let isMounted = true;
fetchData();
return () => {
isMounted = false;
};


// simulate some Web API fetching
function fetchData() {
setTimeout(() => {
// drop "if (isMounted)" to trigger error again
// (take IDE, doesn't work with stack snippet)
if (isMounted) setState("data fetched")
else console.log("aborted setState on unmounted component")
}, 4000);
}
}, []);


return <div>Child: {state}</div>;
};


ReactDOM.render(<Parent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

扩展:自定义useAsync钩子

我们可以将所有样板包封装到一个自定义Hook中,当组件卸载或依赖值之前发生更改时,该Hook会自动中止异步函数:

function useAsync(asyncFn, onSuccess) {
useEffect(() => {
let isActive = true;
asyncFn().then(data => {
if (isActive) onSuccess(data);
});
return () => { isActive = false };
}, [asyncFn, onSuccess]);
}

// custom Hook for automatic abortion on unmount or dependency change
// You might add onFailure for promise errors as well.
function useAsync(asyncFn, onSuccess) {
useEffect(() => {
let isActive = true;
asyncFn().then(data => {
if (isActive) onSuccess(data)
else console.log("aborted setState on unmounted component")
});
return () => {
isActive = false;
};
}, [asyncFn, onSuccess]);
}


const Child = () => {
const [state, setState] = useState("loading (4 sec)...");
useAsync(simulateFetchData, setState);
return <div>Child: {state}</div>;
};


const Parent = () => {
const [mounted, setMounted] = useState(true);
return (
<div>
Parent:
<button onClick={() => setMounted(!mounted)}>
{mounted ? "Unmount" : "Mount"} Child
</button>
{mounted && <Child />}
<p>
Unmount Child, while it is still loading. It won't set state later on,
so no error is triggered.
</p>
</div>
);
};


const simulateFetchData = () => new Promise(
resolve => setTimeout(() => resolve("data fetched"), 4000));


ReactDOM.render(<Parent />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

关于效果清理的更多信息:过度反应:使用效果的完整指南

如果上述解决方案不工作,试试这个,它为我工作:

componentWillUnmount() {
// fix Warning: Can't perform a React state update on an unmounted component
this.setState = (state,callback)=>{
return;
};
}

我也遇到过类似的问题,并解决了它:

我通过在redux上调度一个动作自动使用户登录 (在redux状态下放置认证令牌)

. (

然后我试图用这个显示一条消息。设置状态({succ_message:“……”) 在我的组件中

组件看起来是空的,控制台上出现相同的错误:“卸载组件”..“内存泄漏”等。

在我看完沃尔特在这个帖子里的回答之后

我注意到在我的应用程序的路由表中, 如果用户登录,我的组件的路由无效:

{!this.props.user.token &&
<div>
<Route path="/register/:type" exact component={MyComp} />
</div>
}

无论令牌是否存在,我都使Route可见。

我也有类似的问题,谢谢@ford04帮了我。

但是,出现了另一个错误。

NB。我正在使用ReactJS钩子

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 />
</>
)

这不能工作,因为尽管你重定向到新页面,所有的状态和道具都在dom上被操纵或只是渲染到前一页没有停止。

我找到了什么解决方案

import {Redirect} from 'react-router-dom'


if (true) {
return <redirect to="/new-route" />
}
return (
<>
<render component />
</>
)

基于@ford04的答案,下面是封装在一个方法中的相同内容:

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> ... ;
} ;

如果您正在从axios中获取数据,而错误仍然发生,只需将setter包装在条件中

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;
};
}, []);

取决于你如何打开你的网页,你可能不会引起挂载。例如使用<Link/>返回到已经挂载在虚拟DOM中的页面,因此需要componentDidMount生命周期中的数据会被捕获。

有一个相当常见的钩子,叫做useIsMounted,可以解决这个问题(对于功能组件)…

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); }
})
});
...
}

受@ford04接受的答案的启发,我有更好的方法来处理它,而不是在useAsync中使用useEffect,创建一个返回componentWillUnmount的回调的新函数:

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)
},[])


受@ford04 answer的启发,我使用了这个钩子,它也接受success, errors, finally和abortFn的回调:

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])
}

如果asyncFn正在从后端进行某种类型的读取,那么在组件卸载时中止它通常是有意义的(尽管并非总是如此,有时如果ie。你正在加载一些数据到存储中,你可能只是想要完成它,即使组件是unmounted)

isMounted方法在大多数情况下是一种反模式,因为它实际上并不清理/取消任何东西,它只是避免更改未挂载组件的状态,但对挂起的异步任务不做任何操作。React团队最近删除泄漏警告,因为用户不断创建大量反模式来隐藏警告,而不是修复其原因。

但是用纯JS编写可取消的代码确实很棘手。为了解决这个问题,我用自定义钩子创建了自己的库useAsyncEffect2,构建在一个可取消的承诺(c-promise2)之上,用于执行可取消的异步代码以实现其优雅的取消。所有异步阶段(承诺),包括深层的,都是可以取消的。这意味着如果其父上下文被取消,这里的请求将自动中止。当然,可以使用任何其他异步操作来代替请求。

  • useAsyncEffect演示与普通的useState使用(现场演示):
    import React, { useState } from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpAxios from "cp-axios";
    

function TestComponent({url}) {
const [text, setText] = useState("");
    

const cancel = useAsyncEffect(
function* () {
setText("fetching...");
const json = (yield cpAxios(url)).data;
setText(`Success: ${JSON.stringify(json)}`);
},
[url]
);
    

return (
<div>
<div>{text}</div>
<button onClick={cancel}>
Cancel request
</button>
</div>
);
}
    import React from "react";
import { useAsyncEffect } from "use-async-effect2";
import cpAxios from "cp-axios";
    

function TestComponent({ url, timeout }) {
const [cancel, done, result, err] = useAsyncEffect(
function* () {
return (yield cpAxios(url).timeout(timeout)).data;
},
{ states: true, deps: [url] }
);
    

return (
<div>
{done ? (err ? err.toString() : JSON.stringify(result)) : "loading..."}
<button onClick={cancel} disabled={done}>
Cancel async effect (abort request)
</button>
</div>
);
}
import React, { Component } from "react";
import { ReactComponent } from "c-promise2";
import cpAxios from "cp-axios";


@ReactComponent
class TestComponent extends Component {
state = {
text: ""
};


*componentDidMount(scope) {
const { url, timeout } = this.props;
const response = yield cpAxios(url).timeout(timeout);
this.setState({ text: JSON.stringify(response.data, null, 2) });
}


render() {
return (<div>{this.state.text}</div>);
}
}


export default TestComponent;

更多其他例子:

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]);
}
};

来自@ford04的解决方案对我来说不起作用,特别是如果你需要在多个地方使用isMounted(多个useEffect),建议使用useRef,如下所示:

  1. 基本包
"dependencies":
{
"react": "17.0.1",
}
"devDependencies": {
"typescript": "4.1.5",
}


  1. 我的钩子组件
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 () => {}; }, []);

上面的useEffect()示例返回一个回调函数,触发React在更新状态之前完成其生命周期的卸载部分。

这个非常简单的解决方案就是我们所需要的。此外,它的工作方式也不同于@ford04和@ sfleletche提供的虚构语法。顺便说一下,下面来自@ford04的代码片段是纯虚构语法(@sfletche@vinod@guneetgstar@Drew Cordano使用了完全相同的虚构语法)。

< >强劲数据=比;{< / >强 <——虚构的/想象的语法

someAsyncOperation().then(data => {
if (isMounted) setState(data);    // add conditional check
})

我所有的linter和我整个团队的linter都不会接受它,他们报告Uncaught SyntaxError: unexpected token: '=>'。我感到惊讶的是,竟然没有人知道这个虚构的语法。有谁参与了这个问题的讨论,特别是那些支持他们的人,能不能向我解释一下他们是如何找到对他们有用的解决方案的?

更新不要使用我原来的答案,因为它不起作用

这个答案是基于可取消承诺的使用和makecancelable中的一个注释,我迁移到使用钩子。然而,它似乎并没有取消async/await甚至cancelable-promise 不支持取消等待链的链

对此做了更多的研究,似乎一些内部的谷歌原因阻止了可取消承诺进入标准

此外,“蓝鸟”有一些承诺,引入了可取消的承诺,但它在世博会上不起作用,至少我还没有看到它在世博会上起作用的例子。

接受的答案是最好的。因为我使用TypeScript,我已经适应了一些修改的代码(我显式设置依赖,因为接受的答案的隐式依赖似乎在我的应用程序上重新呈现循环,添加并使用async/await而不是承诺链,传递一个引用到挂载对象,以便async/await链可以在需要时提前取消)

/**
* This starts an async function and executes another function that performs
* React state changes if the component is still mounted after the async
* operation completes
* @template T
* @param {(mountedRef: React.MutableRefObject<boolean>) => Promise<T>} asyncFunction async function,
*   it has a copy of the mounted ref so an await chain can be canceled earlier.
* @param {(asyncResult: T) => void} onSuccess this gets executed after async
*   function is resolved and the component is still mounted
* @param {import("react").DependencyList} deps
*/
export function useAsyncSetEffect(asyncFunction, onSuccess, deps) {
const mountedRef = useRef(false);
useEffect(() => {
mountedRef.current = true;
(async () => {
const x = await asyncFunction(mountedRef);
if (mountedRef.current) {
onSuccess(x);
}
})();
return () => {
mountedRef.current = false;
};
}, deps);
}

原来的答案

由于我有许多不同的操作都是async,所以我使用cancelable-promise包以最小的代码更改来解决这个问题。

以前的代码:

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();
},
[]
);


你也可以像这样在一个自定义实用函数中编写它

/**
* This wraps an async function in a cancelable promise
* @param {() => PromiseLike<void>} asyncFunction
* @param {React.DependencyList} deps
*/
export function useCancelableEffect(asyncFunction, deps) {
useEffect(() => {
const cancelablePromise = cancelable(asyncFunction());
return () => cancelablePromise.cancel();
}, deps);
}

这里有一个简单的解决方案。这个警告是由于当我们在后台执行一些取回请求时(因为一些请求需要一些时间),我们从那个屏幕导航回来,然后他们的反应不能更新状态。下面是示例代码。写在每次状态更新之前的这一行。

if(!isScreenMounted.current) return;

这里是完整的代码

import React , {useRef} from 'react'
import { Text,StatusBar,SafeAreaView,ScrollView, StyleSheet } from 'react-native'
import BASEURL from '../constants/BaseURL';
const SearchScreen = () => {
const isScreenMounted = useRef(true)
useEffect(() => {
return () =>  isScreenMounted.current = false
},[])


const ConvertFileSubmit = () => {
if(!isScreenMounted.current) return;
setUpLoading(true)
 

var formdata = new FormData();
var file = {
uri: `file://${route.params.selectedfiles[0].uri}`,
type:`${route.params.selectedfiles[0].minetype}`,
name:`${route.params.selectedfiles[0].displayname}`,
};
         

formdata.append("file",file);
         

fetch(`${BASEURL}/UploadFile`, {
method: 'POST',
body: formdata,
redirect: 'manual'
}).then(response => response.json())
.then(result => {
if(!isScreenMounted.current) return;
setUpLoading(false)
}).catch(error => {
console.log('error', error)
});
}


return(
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<Text>Search Screen</Text>
</ScrollView>
</SafeAreaView>
</>
)
}


export default SearchScreen;




const styles = StyleSheet.create({
scrollView: {
backgroundColor:"red",
},
container:{
flex:1,
justifyContent:"center",
alignItems:"center"
}
})

我通过提供useEffect钩子中使用的所有参数来解决这个问题

代码报告了该错误:

useEffect(() => {
getDistrict({
geonameid: countryId,
subdistrict: level,
}).then((res) => {
......
});
}, [countryId]);

修复后的代码:

useEffect(() => {
getDistrict({
geonameid: countryId,
subdistrict: level,
}).then((res) => {
......
});
}, [countryId,level]);

可以看到,问题解决后,我提供了所有的参数(包括水平参数),应该通过。

为jsx组件添加一个引用,然后检查它是否存在

function Book() {
const ref = useRef();


useEffect(() => {
asyncOperation().then(data => {
if (ref.current) setState(data);
})
});


return <div ref={ref}>content</div>
}

检查组件是否被挂载实际上是一个反模式根据React文档__ABC0警告的解决方案是利用AbortController的使用:

useEffect(() => {
const abortController = new AbortController()   // creating an AbortController
fetch(url, { signal: abortController.signal })  // passing the signal to the query
.then(data => {
setState(data)                              // if everything went well, set the state
})
.catch(error => {
if (error.name === 'AbortError') return     // if the query has been aborted, do nothing
throw error
})
  

return () => {
abortController.abort()                       // stop the query by aborting on the AbortController on unmount
}
}, [])

对于不基于Fetch API的异步操作,仍然应该有一种方法来取消这些异步操作,您应该利用这些操作,而不仅仅是检查组件是否被挂载。如果您正在构建自己的API,您可以在其中实现AbortController API来处理它。

更多的上下文,检查组件是否被挂载是一个反模式,如React在内部检查组件是否挂载以显示该警告。再次执行相同的检查只是隐藏警告的一种方法,还有一些更简单的方法来隐藏警告,而不是将这段代码添加到代码库的大部分中。

来源:https://medium.com/doctolib/react-stop-checking-if-your-component-is-mounted-3bb2568a4934

React已删除此警告 但这里有一个更好的解决方案(不仅仅是变通)

useEffect(() => {
const abortController = new AbortController()   // creating an AbortController
fetch(url, { signal: abortController.signal })  // passing the signal to the query
.then(data => {
setState(data)                              // if everything went well, set the state
})
.catch(error => {
if (error.name === 'AbortError') return     // if the query has been aborted, do nothing
throw error
})
  

return () => {
abortController.abort()
}
}, [])

对于这个错误,我有两个解决方案:

  1. 返回:

如果你使用了hookuseEffect,那么在useEffect后面加上一个return

useEffect(() => {
window.addEventListener('mousemove', logMouseMove)
return () => {
window.removeEventListener('mousemove', logMouseMove)
}
}, [])
  1. componentWillUnmount:

如果你使用componentDidMount,那么把componentWillUnmount放在它旁边。

componentDidMount() {
window.addEventListener('mousemove', this.logMouseMove)
}


componentWillUnmount() {
window.removeEventListener('mousemove', this.logMouseMove)
}

OK with:

  componentWillUnmount() {
this._isMounted = false;
}

在我的例子中,问题是组件隐藏了子元素,因为孩子组件的条件发生了变化。

所以我所做的就是改变条件,让子组件显示为总是

发生了什么事:

const ParentComponent:FC = () => {
...
if (someCondition) {
return null;
}


return (
<>
Some cool text here
    

<ChildModalComponent message="this is a cool modal" />
</>
)
}






const ChildModalComponent: FC = () => {
...
const handleSubmit = () => {
setSomeCondition(true);
}
}

因此,在单击submit后,由于父条件(someCondition),模式被自动隐藏。

我是怎么解决的?

我改变了父组件中检查someCondition的位置,因此子组件显示为总是:

const ParentComponent:FC = () => {
...
return (
<>
{!someCondition && <>Some cool text here</>
    

<ChildModalComponent message="this is a cool modal" />
</>
)
}

使用useStateIfMounted代替useState。 你必须使用NPM install use-state-if-mounted

来安装第三方库

我遇到了同样的警告,不是固定的。为了解决这个问题,我删除了useEffect()中的useRef()变量检查 前面的代码是

const varRef = useRef();
useEffect(() => {
if (!varRef.current)
{
}
}, []);

现在,代码是

const varRef = useRef();
useEffect(() => {
//if (!varRef.current)
{
}
}, [])

希望有帮助……

在我的类似登录的屏幕的例子中,获取是在父组件的onClick处理程序中完成的,父组件将该处理程序传递给子组件,子组件将.catch和.finally放在它上面。

在.then情况下,重定向(因此卸载)将作为正常操作发生,只有在获取错误的情况下,子进程才会挂载在屏幕上。

我的解决方案是将setState和所有其他代码从.finally移到.catch,因为子对象肯定会挂载在.catch case中。在.then情况下,由于保证了卸载,所以不需要做任何事情。