fix(web): use stable error boundary retry (#41811)

This commit is contained in:
yyh 2026-09-04 07:57:35 +00:00 committed by GitHub
parent 09f301d969
commit b816754c06
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 57 additions and 48 deletions

View File

@ -8,12 +8,12 @@ import { isLegacyBase401 } from '@/features/account-profile/client'
type Props = Readonly<{
error: Error & { digest?: string }
unstable_retry: () => void
retry: () => void
}>
export default function CommonLayoutError({ error, unstable_retry }: Props) {
export default function CommonLayoutError({ error, retry }: Props) {
const { t } = useTranslation('common')
const { reset } = useQueryErrorResetBoundary()
const { reset: resetQueries } = useQueryErrorResetBoundary()
console.error(error)
@ -32,8 +32,8 @@ export default function CommonLayoutError({ error, unstable_retry }: Props) {
size="small"
variant="secondary"
onClick={() => {
reset()
unstable_retry()
resetQueries()
retry()
}}
>
{t(($) => $['errorBoundary.tryAgain'])}

View File

@ -5,35 +5,48 @@ import { afterEach, describe, expect, it, vi } from 'vite-plus/test'
import CommonLayoutError from '@/app/(commonLayout)/error'
import AppError from '@/app/error'
type ErrorRecoveryProps = Readonly<{
retry: () => void
}>
const routeErrors = [
{
name: 'root error',
renderError: (props: ErrorRecoveryProps) => <AppError error={new Error('failed')} {...props} />,
},
{
name: 'common layout error',
renderError: (props: ErrorRecoveryProps) => (
<CommonLayoutError error={new Error('failed')} {...props} />
),
},
]
describe('route error recovery', () => {
afterEach(() => {
vi.restoreAllMocks()
})
it.each([
{
name: 'root error',
renderError: (retry: () => void) => <AppError error={new Error('failed')} reset={retry} />,
it.each(routeErrors)(
'retries the $name after resetting query errors',
async ({ renderError }) => {
const user = userEvent.setup()
const retry = vi.fn()
vi.spyOn(console, 'error').mockImplementation(() => {})
render(
<QueryErrorResetBoundary>
{({ isReset }) =>
renderError({
retry: () => retry(isReset()),
})
}
</QueryErrorResetBoundary>,
)
await user.click(screen.getByRole('button', { name: 'common.errorBoundary.tryAgain' }))
expect(retry).toHaveBeenCalledWith(true)
},
{
name: 'common layout error',
renderError: (retry: () => void) => (
<CommonLayoutError error={new Error('failed')} unstable_retry={retry} />
),
},
])('resets failed queries before retrying the $name', async ({ renderError }) => {
const user = userEvent.setup()
const retry = vi.fn()
vi.spyOn(console, 'error').mockImplementation(() => {})
render(
<QueryErrorResetBoundary>
{({ isReset }) => renderError(() => retry(isReset()))}
</QueryErrorResetBoundary>,
)
await user.click(screen.getByRole('button', { name: 'common.errorBoundary.tryAgain' }))
expect(retry).toHaveBeenCalledWith(true)
})
)
})

View File

@ -6,16 +6,14 @@ import { useTranslation } from 'react-i18next'
import { FullScreenLoading } from '@/app/components/full-screen-loading'
import { isLegacyBase401 } from '@/features/account-profile/client'
type Props = {
type Props = Readonly<{
error: Error & { digest?: string }
reset?: () => void
unstable_retry?: () => void
}
retry: () => void
}>
export default function AppError({ error, reset, unstable_retry }: Props) {
export default function AppError({ error, retry }: Props) {
const { t } = useTranslation('common')
const { reset: resetQueries } = useQueryErrorResetBoundary()
const retry = reset ?? unstable_retry
console.error(error)
@ -26,18 +24,16 @@ export default function AppError({ error, reset, unstable_retry }: Props) {
<div className="system-sm-regular text-text-tertiary">
{t(($) => $['errorBoundary.message'])}
</div>
{retry && (
<Button
size="small"
variant="secondary"
onClick={() => {
resetQueries()
retry()
}}
>
{t(($) => $['errorBoundary.tryAgain'])}
</Button>
)}
<Button
size="small"
variant="secondary"
onClick={() => {
resetQueries()
retry()
}}
>
{t(($) => $['errorBoundary.tryAgain'])}
</Button>
</div>
)
}