Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): render React component stack when available #1564

Merged
merged 1 commit into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions packages/nextclade-web/src/components/Error/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
import React, { ReactNode } from 'react'

import React, { ReactNode, useState, useCallback, useMemo, ErrorInfo } from 'react'
import { ErrorBoundary as ErrorBoundaryBase, FallbackProps } from 'react-error-boundary'
import ErrorPage from 'src/pages/_error'

export function ErrorFallback({ error }: FallbackProps) {
return <ErrorPage error={error} />
export interface ExtendedFallbackProps extends FallbackProps {
errorInfo?: ErrorInfo
}

export function ErrorFallback({ error, errorInfo }: ExtendedFallbackProps) {
return <ErrorPage error={error} errorInfo={errorInfo} />
}

export interface ErrorBoundaryProps {
children?: ReactNode
}

export function ErrorBoundary({ children }: ErrorBoundaryProps) {
return <ErrorBoundaryBase FallbackComponent={ErrorFallback}>{children}</ErrorBoundaryBase>
const [errorInfo, setErrorInfo] = useState<ErrorInfo | undefined>(undefined)

const FallbackComponent = useMemo(() => {
return function ErrorFallbackMemoized(props: FallbackProps) {
return <ErrorFallback {...props} errorInfo={errorInfo} />
}
}, [errorInfo])

const handleError = useCallback((_: Error, info: ErrorInfo) => {
setErrorInfo(info)
}, [])

const handleReset = useCallback(() => {
setErrorInfo(undefined)
}, [])

return (
<ErrorBoundaryBase FallbackComponent={FallbackComponent} onError={handleError} onReset={handleReset}>
{children}
</ErrorBoundaryBase>
)
}
4 changes: 2 additions & 2 deletions packages/nextclade-web/src/components/Error/ErrorContent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from 'react'
import React, { ErrorInfo, useCallback, useMemo, useState } from 'react'
import { Button, Col, Row } from 'reactstrap'
import { useTranslationSafe } from 'src/helpers/useTranslationSafe'
import styled from 'styled-components'
Expand Down Expand Up @@ -56,7 +56,7 @@ export function ErrorContentMessage({ error }: { error: Error }) {
return <ErrorGeneric error={error} />
}

export function ErrorContent(props: { error?: unknown; detailed?: boolean }) {
export function ErrorContent(props: { error?: unknown; errorInfo?: ErrorInfo; detailed?: boolean }) {
const { t } = useTranslationSafe()
const error = useMemo(() => sanitizeError(props.error), [props.error])

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React, { ErrorInfo, useMemo } from 'react'

import styled from 'styled-components'
import { useTranslationSafe as useTranslation } from 'src/helpers/useTranslationSafe'
Expand All @@ -18,11 +18,15 @@ const ExplanationText = styled.section`
hyphens: auto;
`

export function getErrorStackText(error: Error) {
export function getErrorStackText(error?: Error) {
return error?.stack?.replace(/webpack-internal:\/{3}\.\//g, '')?.replace(/https?:\/\/(.+):\d+\//g, '')
}

export function getErrorReportText(error: Error) {
export function getComponentStackText(errorInfo?: ErrorInfo) {
return errorInfo?.componentStack.replace(/webpack-internal:\/{3}\.\//g, '')?.replace(/https?:\/\/(.+):\d+\//g, '')
}

export function getErrorReportText(error: Error, errorInfo?: ErrorInfo) {
const bowser = typeof window !== 'undefined' ? Bowser.parse(window?.navigator?.userAgent) : 'N/A'

return `
Expand All @@ -39,6 +43,10 @@ Browser details: ${JSON.stringify(bowser)}
Call stack:

${getErrorStackText(error) ?? 'N/A'}

Component stack:

${getComponentStackText(errorInfo) ?? 'N/A'}
`
}

Expand Down
11 changes: 6 additions & 5 deletions packages/nextclade-web/src/pages/_error.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React, { ErrorInfo, useMemo } from 'react'
import type { NextPageContext } from 'next'
import { Col, Container as ContainerBase, Row } from 'reactstrap'
import get from 'lodash/get'
Expand Down Expand Up @@ -39,10 +39,11 @@ export const MainContent = styled.main`
export interface ErrorPageProps {
statusCode?: number
title?: string
error?: Error | undefined
error?: Error
errorInfo?: ErrorInfo
}

function ErrorPage({ statusCode, title, error }: ErrorPageProps) {
function ErrorPage({ statusCode, title, error, errorInfo }: ErrorPageProps) {
const { t } = useTranslationSafe()

const titleText = useMemo(() => {
Expand All @@ -64,11 +65,11 @@ function ErrorPage({ statusCode, title, error }: ErrorPageProps) {
return (
<Row noGutters>
<Col>
<ErrorContent error={error} detailed />
<ErrorContent error={error} errorInfo={errorInfo} detailed />
</Col>
</Row>
)
}, [error])
}, [error, errorInfo])

return (
<Layout>
Expand Down