反应钩子。无法对未安装的组件执行 React 状态更新
React-hooks. Can't perform a React state update on an unmounted component
我收到这个错误:
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 a useEffect cleanup
function.
开始获取数据并卸载组件时,但函数正在尝试更新已卸载组件的状态。
解决这个问题的最佳方法是什么?
default function Test() {
const [notSeenAmount, setNotSeenAmount] = useState(false)
useEffect(() => {
let timer = setInterval(updateNotSeenAmount, 2000)
return () => clearInterval(timer)
}, [])
async function updateNotSeenAmount() {
let data // here i fetch data
setNotSeenAmount(data) // here is problem. If component was unmounted, i get error.
}
async function anotherFunction() {
updateNotSeenAmount() //it can trigger update too
}
return <button onClick={updateNotSeenAmount}>Push me</button> //update can be triggered manually
}
最简单的解决方案是使用一个局部变量来跟踪组件是否已安装。这是基于 class 方法的常见模式。这是 an example 用钩子实现它的方法:
function Example() {
const [text, setText] = React.useState("waiting...");
React.useEffect(() => {
let isCancelled = false;
simulateSlowNetworkRequest().then(() => {
if (!isCancelled) {
setText("done!");
}
});
return () => {
isCancelled = true;
};
}, []);
return <h2>{text}</h2>;
}
这里是 an alternative with useRef
(见下文)。请注意,对于依赖项列表,此解决方案将不起作用。 ref 的值在第一次渲染后将保持为真。在那种情况下,第一种解决方案更合适。
function Example() {
const isCancelled = React.useRef(false);
const [text, setText] = React.useState("waiting...");
React.useEffect(() => {
fetch();
return () => {
isCancelled.current = true;
};
}, []);
function fetch() {
simulateSlowNetworkRequest().then(() => {
if (!isCancelled.current) {
setText("done!");
}
});
}
return <h2>{text}</h2>;
}
您可以在 GitHub 上展示此解决方案的 React 项目中的此 article. Here is an issue 中找到有关此模式的更多信息。
如果你从 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;
};
}, []);
TL;DR
这是一个CodeSandBox例子
其他答案当然有用,我只是想分享一个我想出的解决方案。
我构建了这个 hook ,它的工作方式与 React 的 useState 一样,但只有在安装组件时才会设置状态。我发现它更优雅,因为您不必在组件中乱用 isMounted 变量!
安装:
npm install use-state-if-mounted
用法:
const [count, setCount] = useStateIfMounted(0);
您可以在钩子的 npm page 上找到更高级的文档。
这里有一个简单的解决方案。此警告是由于当我们在后台执行一些获取请求时(因为某些请求需要一些时间),我们从该屏幕导航回来然后反应无法更新状态。这是这个的示例代码。 在每个状态更新之前写下这一行。
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"
}
})
我收到这个错误:
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 a useEffect cleanup function.
开始获取数据并卸载组件时,但函数正在尝试更新已卸载组件的状态。
解决这个问题的最佳方法是什么?
default function Test() {
const [notSeenAmount, setNotSeenAmount] = useState(false)
useEffect(() => {
let timer = setInterval(updateNotSeenAmount, 2000)
return () => clearInterval(timer)
}, [])
async function updateNotSeenAmount() {
let data // here i fetch data
setNotSeenAmount(data) // here is problem. If component was unmounted, i get error.
}
async function anotherFunction() {
updateNotSeenAmount() //it can trigger update too
}
return <button onClick={updateNotSeenAmount}>Push me</button> //update can be triggered manually
}
最简单的解决方案是使用一个局部变量来跟踪组件是否已安装。这是基于 class 方法的常见模式。这是 an example 用钩子实现它的方法:
function Example() {
const [text, setText] = React.useState("waiting...");
React.useEffect(() => {
let isCancelled = false;
simulateSlowNetworkRequest().then(() => {
if (!isCancelled) {
setText("done!");
}
});
return () => {
isCancelled = true;
};
}, []);
return <h2>{text}</h2>;
}
这里是 an alternative with useRef
(见下文)。请注意,对于依赖项列表,此解决方案将不起作用。 ref 的值在第一次渲染后将保持为真。在那种情况下,第一种解决方案更合适。
function Example() {
const isCancelled = React.useRef(false);
const [text, setText] = React.useState("waiting...");
React.useEffect(() => {
fetch();
return () => {
isCancelled.current = true;
};
}, []);
function fetch() {
simulateSlowNetworkRequest().then(() => {
if (!isCancelled.current) {
setText("done!");
}
});
}
return <h2>{text}</h2>;
}
您可以在 GitHub 上展示此解决方案的 React 项目中的此 article. Here is an issue 中找到有关此模式的更多信息。
如果你从 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;
};
}, []);
TL;DR
这是一个CodeSandBox例子
其他答案当然有用,我只是想分享一个我想出的解决方案。 我构建了这个 hook ,它的工作方式与 React 的 useState 一样,但只有在安装组件时才会设置状态。我发现它更优雅,因为您不必在组件中乱用 isMounted 变量!
安装:
npm install use-state-if-mounted
用法:
const [count, setCount] = useStateIfMounted(0);
您可以在钩子的 npm page 上找到更高级的文档。
这里有一个简单的解决方案。此警告是由于当我们在后台执行一些获取请求时(因为某些请求需要一些时间),我们从该屏幕导航回来然后反应无法更新状态。这是这个的示例代码。 在每个状态更新之前写下这一行。
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"
}
})