Handling Errors

Retry single component after error

This example was originally created as one of several examples I wrote for a post about errors and retry for React Server Components. See that post for a more detailed explanation of the concepts here.

If a component on our page throws an error, we can either catch it at the page level via error.tsx, or we can wrap the error-prone component in an ErrorBoundary so that we can render the rest of the page even if that component fails.

Here's a Page that has a child component that throws an error 50% of the time, wrapped in an ErrorBoundary:

page.tsx
import { ErrorBoundary } from 'react-error-boundary'
import ErrorFallback from './ErrorFallback'

export default function ResettablePage() {
return (
<>
<p>
This page has a component with a 50% chance of throwing an error. If it
does, a Reset button will appear that you can click to reset the
component.
</p>
<p>The rest of the page will render as normal.</p>

<ErrorBoundary FallbackComponent={ErrorFallback}>
<ErrorComponent />
</ErrorBoundary>
</>
)
}

async function ErrorComponent() {
// Simulate a delay so we can see the Reset button spinning
await new Promise((resolve) => setTimeout(resolve, 1000))

if (Math.random() > 0.5) {
throw new Error('Error thrown in component')
}

return (
<p className="border border-blue-700 p-4">
This has a 50% chance of throwing an error, but this time it rendered
fine.
</p>
)
}

If that error is thrown, our ErrorFallback fallback will be rendered. This fallback includes a Reset button that will reset the page when clicked. That process is handled by the retry function, which calls router.refresh() to reset the page.

While the page is being reset, we show a spinner to indicate that the page is loading. This spinner is a simple component that we've created called Spinner. This complicates the example a little, but is useful UX to show your user that something is happening, especially if the component that errored is a heavy or slow-loading one.

error.tsx
'use client'

import { startTransition, useState } from 'react'
import { useRouter } from 'next/navigation'

import Spinner from './Spinner'

export default function ErrorFallback({
error,
resetErrorBoundary,
}: {
error: Error
resetErrorBoundary: () => void
}) {
const router = useRouter()

//tracks the state of our reset button
const [isResetting, setIsResetting] = useState(false)

function retry() {
setIsResetting(true)

startTransition(() => {
router.refresh()
resetErrorBoundary()
setIsResetting(false)
})
}

return (
<div className="border border-orange-700 p-4 text-orange-700">
<p className="m-0 mb-2 p-0">There was an error loading this component</p>
<button
onClick={() => retry()}
disabled={isResetting}
className="button inline-flex items-center gap-4 rounded-md border bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
>
{isResetting ? <Spinner /> : null}
Retry
</button>
</div>
)
}

Our ErrorFallback.tsx component receives a resetErrorBoundary function as a prop, which is passed in automatically by next.js. However, calling resetErrorBoundary() will not reset the page. Instead, we need to call router.refresh() to reset the page. We also wrap this in a startTransition to ensure that the page doesn't flicker when we reset it.

The reset() function does not actually reset the component, or the page, just the state of the error boundary. To retry the render, you need to call router.refresh().

The call to router.refresh() will cause the entire page to re-render, so it's not as surgical as simply reloading a single component. However, it's a good way to give the user a way to recover from an error without having to refresh the entire page. To the user it will look like the component is being reloaded, which is often good enough.

Because router.refresh() works asynchronously (we're going over the network to re-render on the server, after all), but does not return a Promise, we can't use async/await syntax here. Thankfully, we can wrap that call in a startTransition, which will automatically have React update the UI once the new RSC payload is received.

Live Demo

There's a 50% chance that this example will throw an error. If it does, a Reset button will appear that you can click to reset the page. Otherwise, hit the refresh button above the iframe to try it again.

This is an iframe pointing to /examples/errors/reset-boundary

Similar Examples

Previous
Retry page after error