JAVASCRIPT

Robust Error Boundary Composable for Vue 3

Implement a robust error boundary in Vue 3 using the Composition API to gracefully catch and display errors from child components, preventing app crashes.

// composables/useErrorBoundary.js
import { ref, onErrorCaptured } from 'vue';

export function useErrorBoundary() {
  const error = ref(null);

  onErrorCaptured((err, instance, info) => {
    error.value = err;
    console.error('Error captured:', err, instance, info);
    // Return false to prevent the error from propagating further up the component tree
    // or true to let it continue propagating.
    return true; 
  });

  const resetError = () => {
    error.value = null;
  };

  return { error, resetError };
}

// components/ErrorFallback.vue
<template>
  <div v-if="error" class="error-fallback">
    <h3>Oops! Something went wrong.</h3>
    <p>{{ error.message }}</p>
    <button @click="$emit('reset')">Try again</button>
  </div>
  <slot v-else></slot>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue';

defineProps({
  error: Error,
});

defineEmits(['reset']);
</script>

<style scoped>
.error-fallback {
  border: 1px solid #ff0000;
  padding: 15px;
  background-color: #ffe0e0;
  color: #c00;
  border-radius: 8px;
  margin-top: 20px;
}
</style>

// src/App.vue (or any parent component)
<template>
  <ErrorBoundary v-slot="{ hasError, resetError }">
    <ErrorFallback :error="hasError" @reset="resetError">
      <TheContentThatMightFail />
    </ErrorFallback>
  </ErrorBoundary>
</template>

<script setup>
import { defineComponent, h } from 'vue';
import { useErrorBoundary } from './composables/useErrorBoundary';
import ErrorFallback from './components/ErrorFallback.vue';

const ErrorBoundary = defineComponent({
  setup(_, { slots }) {
    const { error, resetError } = useErrorBoundary();

    return () => slots.default({
      hasError: error.value,
      resetError,
    });
  },
});

// A component designed to potentially throw an error
const TheContentThatMightFail = defineComponent({
  setup() {
    const throwError = () => {
      throw new Error('This is a simulated component error!');
    };
    return () => h('div', [
      h('p', 'Content that might fail.'),
      h('button', { onClick: throwError }, 'Trigger Error')
    ]);
  },
});
</script>
How it works: This example provides a robust error boundary solution for Vue 3 using a composable (`useErrorBoundary`) and a renderless component pattern. The `onErrorCaptured` lifecycle hook within `useErrorBoundary` catches errors from descendant components, storing them in a reactive `error` ref. The `ErrorBoundary` component then uses a slot to expose `hasError` and `resetError` to its parent, allowing an `ErrorFallback` component to display the error gracefully or render the original content. This prevents application crashes and improves user experience.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs