Vue Error Handling

Sep 16, 2020

Vue instances have an errorCaptured hook that Vue calls whenever an event handler or lifecycle hook throws an error. For example, the below code will increment a counter because the child component test throws an error every time the button is clicked.

  Vue.component('test', {
    template: '<button v-on:click="notAMethod()">Throw</button>'
  });

  const app = new Vue({
    data: () => ({ count: 0 }),
    errorCaptured: function(err) {
      console.log('Caught error', err.message);
      ++this.count;
      return false;
    },
    template: `
    <div>
      <span id="count">{{count}}</span>
      <test></test>
    </div>
    `
  });

errorCaptured Only Catches Errors in Nested Components

A common gotcha is that Vue does not call errorCaptured when the error occurs in the same component that the errorCaptured hook is registered on. For example, if you remove the 'test' component from the above example and inline the button in the top-level Vue instance, Vue will not call errorCaptured.

  const app = new Vue({
    data: () => ({ count: 0 }),
    // Vue won't call this hook, because the error occurs in this Vue
    // instance, not a child component.
    errorCaptured: function(err) {
      console.log('Caught error', err.message);
      ++this.count;
      return false;
    },
    template: `
    <div>
      <span id="count">{{count}}</span>
      <button v-on:click="notAMethod()">Throw</button>
    </div>
    `
  });

Async Errors

On the bright side, Vue does call errorCaptured() when an async function throws an error. For example, if a child component asynchronously throws an error, Vue will still bubble up the error to the parent.

  Vue.component('test', {
    methods: {
      // Vue bubbles up async errors to the parent's `errorCaptured()`, so
      // every time you click on the button, Vue will call the `errorCaptured()`
      // hook with `err.message = 'Oops'`
      test: async function test() {
        await new Promise(resolve => setTimeout(resolve, 50));
        throw new Error('Oops!');
      }
    },
    template: '<button v-on:click="test()">Throw</button>'
  });

  const app = new Vue({
    data: () => ({ count: 0 }),
    errorCaptured: function(err) {
      console.log('Caught error', err.message);
      ++this.count;
      return false;
    },
    template: `
    <div>
      <span id="count">{{count}}</span>
      <test></test>
    </div>
    `
  });

Error Propagation

You might have noticed the return false line in the previous examples. If your errorCaptured() function does not return false, Vue will bubble up the error to parent components' errorCaptured():

  Vue.component('level2', {
    template: '<button v-on:click="notAMethod()">Throw</button>'
  });

  Vue.component('level1', {
    errorCaptured: function(err) {
      console.log('Level 1 error', err.message);
    },
    template: '<level2></level2>'
  });

  const app = new Vue({
    data: () => ({ count: 0 }),
    errorCaptured: function(err) {
      // Since the level1 component's `errorCaptured()` didn't return `false`,
      // Vue will bubble up the error.
      console.log('Caught top-level error', err.message);
      ++this.count;
      return false;
    },
    template: `
    <div>
      <span id="count">{{count}}</span>
      <level1></level1>
    </div>
    `
  });

On the other hand, if your errorCaptured() function returns false, Vue will stop propagation of that error:

  Vue.component('level2', {
    template: '<button v-on:click="notAMethod()">Throw</button>'
  });

  Vue.component('level1', {
    errorCaptured: function(err) {
      console.log('Level 1 error', err.message);
      return false;
    },
    template: '<level2></level2>'
  });

  const app = new Vue({
    data: () => ({ count: 0 }),
    errorCaptured: function(err) {
      // Since the level1 component's `errorCaptured()` returned `false`,
      // Vue won't call this function.
      console.log('Caught top-level error', err.message);
      ++this.count;
      return false;
    },
    template: `
    <div>
      <span id="count">{{count}}</span>
      <level1></level1>
    </div>
    `
  });

More Vue Tutorials