英文:
Infinite loop in useEffect with constant dependency array using react-error-boundary
问题
In my example below, why is there an infinite loop of errors in the devtools console? It seems like resetErrorBoundary()
is causing useEffect
to trigger again and vice versa leading to an infinite loop, but I don't understand why useEffect
would keep running even with a constant value in its dependency array.
This answer solves the problem by explicitly checking for changes to the dependency array value with an if-statement, but shouldn't useEffect
do that automatically? I would expect an if-statement like that to be redundant.
这个问题的关键似乎在于,resetErrorBoundary()
导致 useEffect
和它互相触发,从而导致无限循环。但是,我不理解为什么 useEffect
会在其依赖数组中使用常量值时继续运行。
此答案 通过明确检查依赖数组值的更改来解决了这个问题,但 useEffect
应该自动处理这一点,我本来期望像这样的 if 语句是多余的。
https://codesandbox.io/p/github/adamerose/error-boundary-example/main?file=%2Fsrc%2FApp.jsx
注意 - 这只是一个最小的示例。我的实际项目中,依赖数组中包含 location.pathname
,因为我希望在 URL 导航时重置错误,但我发现无论我在依赖数组中放什么,都会导致无限循环。
英文:
In my example below, why is there an infinite loop of errors in the devtools console? It seems like resetErrorBoundary()
is causing useEffect
to trigger again and vice versa leading to an infinite loop, but I don't understand why useEffect
would keep running even with a constant value in its dependency array.
This answer solves the problem by explicitly checking for changes to the dependency array value with an if-statement, but shouldn't useEffect
do that automatically? I would expect an if-statement like that to be redundant.
https://codesandbox.io/p/github/adamerose/error-boundary-example/main?file=%2Fsrc%2FApp.jsx
import { useEffect } from "react";
import { ErrorBoundary } from "react-error-boundary";
function ThisComponentWillError() {
throw Error("SomeError");
}
function App() {
return (
<main>
<StandardErrorBoundary>
<ThisComponentWillError />
</StandardErrorBoundary>
</main>
);
}
function ErrorFallback({ error, resetErrorBoundary }) {
useEffect(() => {
resetErrorBoundary();
}, ["CONSTANT"]);
return (
<div>
<p>Something went wrong:</p>
<pre>{error.toString()}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function StandardErrorBoundary({ children }) {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>{children}</ErrorBoundary>
);
}
export default App;
Note - This is just a minimal example. My actual project has location.pathname
in the dependency because I want to reset errors on URL navigation, but I realized no matter what I had in the dependency array it would infinitely loop.
答案1
得分: 1
以下是已经翻译好的内容:
"One way to debug something like this is to inspect the source code of the library being used. Thankfully react-error-boundary
is just one component, it was relatively easier to inspect.
You are assuming that the ErrorFallback
component is re-rendered when resetErrorBoundary
is called. Instead it is completely remounted. Once remounted all effects will run again, cause it is like a new first invocation of the function.
Here is the source code. I have commented the irrelevant part:
const initialState: ErrorBoundaryState = {error: null}
class ErrorBoundary extends React.Component<
React.PropsWithRef<React.PropsWithChildren<ErrorBoundaryProps>>,
ErrorBoundaryState
> {
static getDerivedStateFromError(error: Error) {
return {error}
}
state = initialState
resetErrorBoundary = (...args: Array<unknown>) => {
this.props.onReset?.(...args)
this.reset()
}
reset() {
this.setState(initialState)
}
....
....
render() {
const {error} = this state
const {fallbackRender, FallbackComponent, fallback} = this.props
if (error !== null) {
const props = {
error,
resetErrorBoundary: this.resetErrorBoundary,
}
if (React.isValidElement(fallback)) {
return fallback
} else if (typeof fallbackRender is 'function') {
return fallbackRender(props)
} else if (FallbackComponent) {
return <FallbackComponent {...props} />
} else {
throw new Error(
'react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop',
)
}
}
return this.props.children
}
}
So once resetErrorBoundary
is called. The state is re-initialized and becomes {error:null}
. Now in that case the children of the error boundary wrapped code will be rendered instead of the fallback. In the above case, the child tree again throws an error and hence state becomes something other than {error:null}
. The render method of ErrorBoundary
is called again and this time the Fallback component is rendered because this time
this.state.error` is not null. Hence completing the loop and this goes on.
PS: I was able to find out that the component was getting remounted by running a useEffect
with empty dependency."
英文:
One way to debug something like this is to inspect the source code of the library being. used. Thankfully react-error-boundary
is just one component, it was relatively easier to inspect.
You are assuming that the ErrorFallback
component is re-rendered when resetErrorBoundary
is called. Instead it is completely remounted. Once remounted all effects will run again, cause it is like a new first invocation of the function.
Here is the source code. I have commented the irrelevant part:
const initialState: ErrorBoundaryState = {error: null}
class ErrorBoundary extends React.Component<
React.PropsWithRef<React.PropsWithChildren<ErrorBoundaryProps>>,
ErrorBoundaryState
> {
static getDerivedStateFromError(error: Error) {
return {error}
}
state = initialState
resetErrorBoundary = (...args: Array<unknown>) => {
this.props.onReset?.(...args)
this.reset()
}
reset() {
this.setState(initialState)
}
....
....
render() {
const {error} = this.state
const {fallbackRender, FallbackComponent, fallback} = this.props
if (error !== null) {
const props = {
error,
resetErrorBoundary: this.resetErrorBoundary,
}
if (React.isValidElement(fallback)) {
return fallback
} else if (typeof fallbackRender === 'function') {
return fallbackRender(props)
} else if (FallbackComponent) {
return <FallbackComponent {...props} />
} else {
throw new Error(
'react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop',
)
}
}
return this.props.children
}
}
So once resetErrorBoundary
is called. The state is re-initialized and becomes {error:null}
. Now in that case the children of the error boundary wrapped code will be rendered instead of the fallback. In the above case, the child tree again throws an error and hence state becomes something other than {error:null}
. The render method of ErrorBoundary
is called again and this time the Fallback component is rendered because this time
this.state.error` is not null. Hence completing the loop and this goes on.
PS: I was able to find out that the component was getting remounted by running a useEffect
with empty dependency.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论