skip to Main Content

I am making a render library for my project with Vue 3 + Vite.

Basically I have a json array of products which I pass to special <Render :products /> component.

This Render component reads all json products and converts them into Vue components:

// Render.vue

<script setup lang="ts">
import { Product } from 'bitran';
import { resolveComponent } from 'vue';

const props = defineProps<{ products: Product[] }>();
const products = props.products;
</script>

<template>
    <template v-for="product in products">
        <component :is="resolveComponent(product.type)" :product />
    </template>
</template>

Of course errors can occur when rendering json data to component.

How can I catch these errors during rendering <component ... /> and replace them with my own custom <Error :msg /> component so the rendering process won’t stop and the end-user could see, which product caused the problem and why?

For example, I intentionally throw an Error inside my <Paragraph> component and would like this <Paragraph> component to be converted into <Error> component instead:

// Paragraph.vue

<script setup lang="ts">
import { Paragraph } from 'bitran';
import { getProductAttrs, useProduct } from '@lib/content/util';
import Render from '@lib/Render.vue';

const block = useProduct<Paragraph>();

throw new Error('This message should be passed to <Error> component!'); // Error here
</script>

<template>
    <p v-bind="getProductAttrs(block)">
        <Render :products="block.content" />
    </p>
</template>

2

Answers


  1. You can use the onErrorCaptured hook to capture the error and the component instance, and get its content and show it, then return false in order to not break the app, the second parameter in the hook callback represents the component instance that’s causing the error :

    TryRender.vue

    <script setup lang="ts">
    
    import { onErrorCaptured } from 'vue';
    
    
    const componentsCausingErrors = ref([])
    
    onErrorCaptured((_error, instance) => {
        componentsCausingErrors.value.push(instance.$options.name)
        return false
    })
    </script>
    
    <template>
       {{componentsCausingErrors}}
        <-- other content -->
    </template>
    

    Note that you should give the component an explicit name using the defineOptions macro :

    <script setup>
    throw new Error('Error in component B');
    defineOptions({
      name: 'CompB'
    })
    </script>
    
    <template>
      <div>
        component B
      </div>
    </template>
    
    ```
    
    Login or Signup to reply.
  2. If you want to catch errors in setup() you could overwrite it and add error handling:

    VUE SFC PLAYGROUND

    <script setup>
    import {h} from 'vue';
    import Paragraph from './Paragraph.vue';
    import ParagraphOK from './ParagraphOK.vue';
    import Error from './Error.vue';
    
    const SafeComponent = (props, {slots}) => {
      let component;
      // extract 'is' property
      ({is:component, ...props} = props);
      // overwrite the setup function
      if(component.setup){
        let _setup = component.setup;
        // make a shallow copy of the component to avoid changing original components
        component = {...component, setup: function(props, extra){
          try{
            return _setup.call(component, props, extra ?? {});
          }catch(e){
            // return a render function with rendering the error component
            return () => h(Error, {message: e.message});
          }
        }};
      }
      return h(component, props, slots);
    };
    </script>
    
    <template>    
            <safe-component :is="Paragraph"><template #header>Header</template>Paragraph</safe-component>
            <safe-component :is="ParagraphOK" product="Product">OK paragraph</safe-component>
    </template>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search