feat(nuxt3): add <NuxtErrorBoundary> component for fine-grained error handling (#3671)

* feat(nuxt3): add `<NuxtErrorBoundary>` component for fine-grained error handling

* feat: add `@error` event handling

* fix: don't clear error on nav

* fix: remove `clearError` wrapper

* fix: remove outdated implementation

* update clear error

* upddate example with FaultyComponent

Co-authored-by: Pooya Parsa <pyapar@gmail.com>
This commit is contained in:
Daniel Roe 2022-03-16 15:49:53 +00:00 committed by GitHub
parent 6d2625925f
commit 12304909bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 94 additions and 0 deletions

View File

@ -88,3 +88,32 @@ You can call this function at any point on client-side, or (on server side) dire
* `function clearError (redirect?: string): Promise<void>` * `function clearError (redirect?: string): Promise<void>`
This function will clear the currently handled Nuxt error. It also takes an optional path to redirect to (for example, if you want to navigate to a 'safe' page). This function will clear the currently handled Nuxt error. It also takes an optional path to redirect to (for example, if you want to navigate to a 'safe' page).
## Rendering errors within your app
Nuxt also provides a `<NuxtErrorBoundary>` component that allows you to handle client-side errors within your app, without replacing your entire site with an error page.
This component is responsible for handling errors that occur within its default slot. On client-side, it will prevent the error from bubbling up to the top level, and will render the `#error` slot instead.
The `#error` slot will receive `error` as a prop. (If you set `error = null` it will trigger re-rendering the default slot; you'll need to ensure that the error is fully resolved first or the error slot will just be rendered a second time.)
::alert{type="info"}
If you navigate to another route, the error will be cleared automatically.
::
### Example
```vue [pages/index.vue]
<template>
<!-- some content -->
<NuxtErrorBoundary @error="someErrorLogger">
<!-- You use the default slot to render your content -->
<template #error="{ error }">
You can display the error locally here.
<button @click="error = null">
This will clear the error.
</button>
</template>
</NuxtErrorBoundary>
</template>
```

View File

@ -21,6 +21,9 @@ function triggerError () {
<NuxtLink to="/" class="n-link-base"> <NuxtLink to="/" class="n-link-base">
Home Home
</NuxtLink> </NuxtLink>
<NuxtLink to="/other" class="n-link-base">
Other
</NuxtLink>
<NuxtLink to="/404" class="n-link-base"> <NuxtLink to="/404" class="n-link-base">
404 404
</NuxtLink> </NuxtLink>
@ -36,6 +39,8 @@ function triggerError () {
</nav> </nav>
</template> </template>
<FaultyComponent />
<template #footer> <template #footer>
<div class="text-center p-4 op-50"> <div class="text-center p-4 op-50">
Current route: <code>{{ route.path }}</code> Current route: <code>{{ route.path }}</code>

View File

@ -0,0 +1,25 @@
<script setup>
const hasIssue = ref(true)
const fixIssue = (error) => {
hasIssue.value = false
error.value = null
}
</script>
<template>
<NuxtErrorBoundary>
<throw-error v-if="hasIssue" />
<div v-else>
Component is working ^_^
</div>
<template #error="{ error }">
Component failed to Render -_-
<button @click="fixIssue(error)">
(fix the issue)
</button>
</template>
</NuxtErrorBoundary>
</template>

View File

@ -0,0 +1,7 @@
<script setup>
throw new Error('Deliberate error by <ThrowError>')
</script>
<template>
<div>Should never see this</div>
</template>

View File

@ -4,12 +4,15 @@
<h1>{{ error.message }}</h1> <h1>{{ error.message }}</h1>
There was an error 😱 There was an error 😱
<br>
<button @click="handleError"> <button @click="handleError">
Clear error Clear error
</button> </button>
<br>
<NuxtLink to="/404"> <NuxtLink to="/404">
Trigger another error Trigger another error
</NuxtLink> </NuxtLink>
<br>
<NuxtLink to="/"> <NuxtLink to="/">
Navigate home Navigate home
</NuxtLink> </NuxtLink>

View File

View File

@ -0,0 +1,19 @@
import { defineComponent, ref, onErrorCaptured } from 'vue'
import { useNuxtApp } from '#app'
export default defineComponent({
setup (_props, { slots, emit }) {
const error = ref(null)
const nuxtApp = useNuxtApp()
onErrorCaptured((err) => {
if (process.client && !nuxtApp.isHydrating) {
emit('error', err)
error.value = err
return false
}
})
return () => error.value ? slots.error?.({ error }) : slots.default?.()
}
})

View File

@ -84,6 +84,12 @@ async function initNuxt (nuxt: Nuxt) {
filePath: resolve(nuxt.options.appDir, 'components/layout') filePath: resolve(nuxt.options.appDir, 'components/layout')
}) })
// Add <NuxtErrorBoundary>
addComponent({
name: 'NuxtErrorBoundary',
filePath: resolve(nuxt.options.appDir, 'components/nuxt-error-boundary')
})
// Add <ClientOnly> // Add <ClientOnly>
addComponent({ addComponent({
name: 'ClientOnly', name: 'ClientOnly',