Skip to content

Commit

Permalink
feat(react): prevent recursive expose fallback when fallback throw error
Browse files Browse the repository at this point in the history
  • Loading branch information
manudeli committed Jan 6, 2025
1 parent 07b6b77 commit 5e08d23
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 6 deletions.
22 changes: 22 additions & 0 deletions packages/react/src/ErrorBoundary.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,28 @@ describe('<ErrorBoundary/>', () => {
await waitFor(() => expect(screen.queryByText(errorText)).toBeInTheDocument())
}
)

it('should re-throw error occurred by fallback', async () => {
render(
<ErrorBoundary fallback={() => <>This is expected</>}>
<ErrorBoundary
fallback={() => (
<Throw.Error message={ERROR_MESSAGE} after={100}>
ErrorBoundary's fallback before error
</Throw.Error>
)}
>
<Throw.Error message={ERROR_MESSAGE} after={100}>
ErrorBoundary's children before error
</Throw.Error>
</ErrorBoundary>
</ErrorBoundary>
)

expect(screen.queryByText("ErrorBoundary's children before error")).toBeInTheDocument()
await waitFor(() => expect(screen.queryByText("ErrorBoundary's fallback before error")).toBeInTheDocument())
await waitFor(() => expect(screen.queryByText('This is expected')).toBeInTheDocument())
})
})

describe('<ErrorBoundary.Consumer/>', () => {
Expand Down
34 changes: 28 additions & 6 deletions packages/react/src/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ class BaseErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState
}

componentDidCatch(error: Error, info: ErrorInfo) {
if (error instanceof InternalFallbackError) {
throw error.fallbackError
}
if (error instanceof SuspensiveError) {
throw error
}
Expand Down Expand Up @@ -147,12 +150,12 @@ class BaseErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState
throw error
}

if (typeof fallback === 'function') {
const FallbackComponent = fallback
childrenOrFallback = <FallbackComponent error={error} reset={this.reset} />
} else {
childrenOrFallback = fallback
}
const Fallback = fallback
childrenOrFallback = (
<InternalFallbackErrorBoundary>
{typeof Fallback === 'function' ? <Fallback error={error} reset={this.reset} /> : Fallback}
</InternalFallbackErrorBoundary>
)
}

return (
Expand All @@ -163,6 +166,25 @@ class BaseErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState
}
}

class InternalFallbackError extends Error {
fallbackError: Error
constructor(error: Error) {
super(error.message)
this.fallbackError = error
}
}
class InternalFallbackErrorBoundary extends Component<{ children: ReactNode }> {
componentDidCatch(fallbackError: Error) {
if (fallbackError instanceof SuspensiveError) {
throw fallbackError
}
throw new InternalFallbackError(fallbackError)
}
render() {
return this.props.children
}
}

/**
* This component provides a simple and reusable wrapper that you can use to wrap around your components. Any rendering errors in your components hierarchy can then be gracefully handled.
* @see {@link https://suspensive.org/docs/react/ErrorBoundary Suspensive Docs}
Expand Down

0 comments on commit 5e08d23

Please sign in to comment.