DEV Community

Cover image for 25 Vue Tips You Need to Know
Michael Thiessen
Michael Thiessen

Posted on • Updated on

25 Vue Tips You Need to Know

Learning to be a better Vue developer isn't always about the big concepts that take time and effort to master.

It's also about the short tips and tricks that can make your life a whole lot easier — without a whole lot of work.

I've picked up a ton of useful tips over the years developing and writing about Vue. Some are clever, some I use almost every day, and some are more advanced — but they're all useful.

All of these were first published in my weekly newsletter. Make sure to sign up if you want more great tips like these!

1. Restrict a prop to a list of types

Using the validator option in a prop definition you can restrict a prop to a specific set of values:

export default {
  name: 'Image',
  props: {
    src: {
      type: String,
    },
    style: {
      type: String,
      validator: s => ['square', 'rounded'].includes(s)
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

This validator function takes in a prop, and returns either true or false — if the prop is valid or not.

I often use this when I need more options than a boolean will allow, but still want to restrict what can be set.

Button types or alert types (info, success, danger, warning) are some of the most common uses — at least in what I work on. Colours, too, are a really great use for this.

But there are many more!

2. Default Content and Extension Points

Slots in Vue can have default content, which allows you to make components that are much easier to use:

<button class="button" @click="$emit('click')">
  <slot>
    <!-- Used if no slot is provided -->
    Click me
  </slot>
</button>
Enter fullscreen mode Exit fullscreen mode

My favourite use for default slots though, is using them to create extension points.

Basically, you take any part of a component, wrap it in a slot, and now you can override that part of the component with whatever you want. By default it'll still work the way it always has, but now you have more options:

<template>
  <button class="button" @click="$emit('click')">
    <!-- Adding in the slot tag does nothing at first -->
    <!-- We can override this by providing content to the slot -->
    <slot>
      <div class="formatting">
        {{ text }}
      </div>
    </slot>
  </button>
</template>
Enter fullscreen mode Exit fullscreen mode

Now you can use this component in many different ways. The easy, default way, or your own, custom way:

<!-- Uses default functionality of the component -->
<ButtonWithExtensionPoint text="Formatted text" />

<!-- Use the extension point to create custom behaviour -->
<ButtonWithExtensionPoint>
  <div class="different-formatting">
    Do something a little different here
  </div>
</ButtonWithExtensionPoint>
Enter fullscreen mode Exit fullscreen mode

Here's a Codesandbox you can dive into:

https://codesandbox.io/s/default-content-and-extension-points-bl87m?file=/src/App.vue

3. Use quotes to watch nested values

You may not have known this, but you can easily watch nested values directly, just by using quotes:

watch {
  '$route.query.id'() {
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

This is really useful for working with deeply nested objects!

4. Know when to use v-if (and when to avoid it)

Instead of using v-if, it's sometimes more performant to use v-show instead:

<ComplicatedChart v-show="chartEnabled" />
Enter fullscreen mode Exit fullscreen mode

When v-if is toggled on and off it will create and destroy the element completely. Instead, v-show will create the element and leave it there, hiding it by setting it's style to display: none.

Doing this can be much more efficient if the component you're toggling is expensive to render.

On the flip side, if you don't need that expensive component immediately, use v-if so that it will skip rendering it and load the page just a bit faster.

5. Shorthand for single scoped slot (no template tag needed!)

Scoped slots are lots of fun, but in order to use them you have to use a lot of template tags, too.

Luckily, there's a shorthand that let's us get rid of it, but only if we're using a single scoped slot.

Instead of writing this:

<DataTable>
  <template #header="tableAttributes">
    <TableHeader v-bind="tableAttributes" />
  </template>
</DataTable>
Enter fullscreen mode Exit fullscreen mode

We can write this:

<DataTable #header="tableAttributes">
  <TableHeader v-bind="tableAttributes" />
</DataTable>
Enter fullscreen mode Exit fullscreen mode

Simple, straightforward, and marvelous.

(Ok, maybe not quite marvelous, but still pretty good)

All of these tips were first published in my weekly newsletter. Make sure to sign up if you want more great tips like these!

6. Conditionally Rendering Slots (and why you'd need to)

First I'll show you how, then we'll get into why you'd want to hide slots.

Every Vue component has a special $slots object with all of your slots in it. The default slot has the key default, and any named slots use their name as the key:

const $slots = {
  default: <default slot>,
  icon: <icon slot>,
  button: <button slot>,
};
Enter fullscreen mode Exit fullscreen mode

But this $slots object only has the slots that are applied to the component, not every slot that is defined.

Take this component that defines several slots, including a couple named ones:

<!-- Slots.vue -->
<template>
  <div>
    <h2>Here are some slots</h2>
    <slot />
    <slot name="second" />
    <slot name="third" />
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

If we only apply one slot to the component, only that slot will show up in our $slots object:

<template>
  <Slots>
    <template #second>
      This will be applied to the second slot.
    </template>
  </Slots>
</template>
Enter fullscreen mode Exit fullscreen mode
$slots = { second: <vnode> }
Enter fullscreen mode Exit fullscreen mode

We can use this in our components to detect which slots have been applied to the component, for example, by hiding the wrapper element for the slot:

<template>
  <div>
    <h2>A wrapped slot</h2>
    <div v-if="$slots.default" class="styles">
      <slot />
    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Now the wrapper div that applies the styling will only be rendered if we actually fill that slot with something.

If we don't use the v-if, we would end up with an empty and unnecessary div if we didn't have a slot. Depending on what styling that div has, this could mess up our layout and make things look weird.

So why do we want to be able to conditionally render slots?

There are three main reasons to use a conditional slot:

  1. When using wrapper divs to add default styles
  2. The slot is empty
  3. If we're combining default content with nested slots

For example, when we're adding default styles, we're adding a div around a slot:

<template>
  <div>
    <h2>This is a pretty great component, amirite?</h2>
    <div class="default-styling">
      <slot >
    </div>
    <button @click="$emit('click')">Click me!</button>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

However, if no content is applied to that slot by the parent component, we'll end up with an empty div rendered to the page:

<div>
  <h2>This is a pretty great component, amirite?</h2>
  <div class="default-styling">
    <!-- No content in the slot, but this div
          is still rendered. Oops. -->
  </div>
  <button @click="$emit('click')">Click me!</button>
</div>
Enter fullscreen mode Exit fullscreen mode

Adding that v-if on the wrapping div solves the problem though. No content applied to the slot? No problem:

<div>
  <h2>This is a pretty great component, amirite?</h2>
  <button @click="$emit('click')">Click me!</button>
</div>
Enter fullscreen mode Exit fullscreen mode

Here's a Codesandbox with a working demo if you want to take a look: https://codesandbox.io/s/reactive-slots-bth28?file=/src/components/HasSlot.vue

I wrote more tips on slots in this article: Tips to Supercharge Your Slots (Named, Scoped, and Dynamic)

7. How to watch a slot for changes

This tip comes from Austin Gil — check out his awesome blog post on this here.

Sometimes we need to know when the content inside of a slot has changed:

<!-- Too bad this event doesn't exist -->
<slot @change="update" />
Enter fullscreen mode Exit fullscreen mode

Unfortunately, Vue has no built-in way for us to detect this.

However, my friend Austin figured out a very clean way of doing this using a mutation observer:

export default {
  mounted() {
    // Call `update` when something changes
    const observer = new MutationObserver(this.update);

    // Watch this component for changes
    observer.observe(this.$el, {
      childList: true,
      subtree: true
    });
  }
};
Enter fullscreen mode Exit fullscreen mode

You'll also need to clean up the observer, but Austin covers that, and more, in his article.

8. Mixing local and global styles together

Normally, when working with styles we want them to be scoped to a single component:

<style scoped>
  .component {
    background: green;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

In a pinch though, you can also add a non-scoped style block to add in global styles if you need it:

<style>
  /* Applied globally */
  .component p {
    margin-bottom: 16px;
  }
</style>

<style scoped>
  /* Scoped to this specific component */
  .component {
    background: green;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

Be careful though — global styles are dangerous and hard to track down. Sometimes, though, they're the perfect escape hatch and are exactly what you need.

9. Overriding styles of a child component — the right way

Scoped CSS is fantastic for keeping things tidy, and not accidentally bleeding styles into other parts of your app.

But sometimes you need to override the styles of a child component, and break out of that scope.

Vue has a deep selector just for this:

<style scoped>
/* Override CSS of a child component
   while keeping styles scoped */
.my-component >>> .child-component {
  font-size: 24px;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Yes, a couple months ago I covered exactly why you shouldn't do this, but overriding styles can be the best solution (we don't believe in "best practices" here).

Note: If you're using a CSS pre-processor like SCSS, you may need to use /deep/ instead.

10. Creating Magic with Context-Aware Components

Context-aware components are "magical" — they adapt to what's going on around them automatically, handling edge cases, state sharing, and more.

There are 3 main types of context-aware components, but configuration is the one I find most interesting.

1. State Sharing

When you break up a large component into multiple smaller ones, they often still need to share state.

Instead of pushing that work on whoever's consuming the components, you can make this happen "behind the scenes".

You may break up a Dropdown component into Select and Option components to give you more flexibility. But to make it easier to use, the Select and Option components share the selected state with each other:

<!-- Used as a single component for simplicity -->
<Dropdown v-model="selected" :options="[]" />

<!-- Split up for more flexibility -->
<Select v-model="selected">
  <Option value="mustard">Mustard</Option>
  <Option value="ketchup">Ketchup</Option>
  <div class="relish-wrapper">
    <Option value="relish">Relish</Option>
  </div>
</Select>
Enter fullscreen mode Exit fullscreen mode

2. Configuration

Sometimes the behaviour of a component needs to be changed based on what's going on in the rest of the application. This is often done to automagically handle edge cases that would otherwise be annoying to deal with.

A Popup or Tooltip should re-position itself so it doesn't overflow out of the page. But if that component is inside of a modal, it should re-position itself so it doesn't overflow out of the modal.

This can be done automagically if the Tooltip knows when it's inside of a modal.

3. Styling

You already create context-aware CSS, applying different styles based on what's happening in parent or sibling elements.

.statistic {
  color: black;
  font-size: 24px;
  font-weight: bold;
}

/* Give some separation between stats
   that are right beside each other */
.statistic + .statistic {
  margin-left: 10px;
}
Enter fullscreen mode Exit fullscreen mode

CSS variables let us push this even further, allowing us to set different values in different parts of a page.

Check out this thread on Twitter if you want to discuss this concept!

Exclusive tips and insights every week

Join 8135 other Vue devs and get exclusive tips and insights like these delivered straight to your inbox, every week.

You have great content in your emails. I seriously learn something from every one of them. — Titus Decali

Thanks for another beautiful tip 🙏 — Victor Onuoha

Loving these, and the spaced repetition — Mark Goldstein

Sign up here

11. How to make a variable created outside of Vue reactive (Vue 2 and 3)

If you get a variable from outside of Vue, it's nice to be able to make it reactive.

That way you can use it in computed props, watchers, and everywhere else, and it works just like any other state in Vue.

If you're using the options API, all you need is to put it in the data section of your component:

const externalVariable = getValue();

export default {
  data() {
    return {
      reactiveVariable: externalVariable,
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

If you're using the composition API with Vue 3, you can use ref or reactive directly:

import { ref } from 'vue';

// Can be done entirely outside of a Vue component
const externalVariable = getValue();
const reactiveVariable = ref(externalVariable);

// Access using .value
console.log(reactiveVariable.value);
Enter fullscreen mode Exit fullscreen mode

Using reactive instead:

import { reactive } from 'vue';

// Can be done entirely outside of a Vue component
const externalVariable = getValue();
// Reactive only works with objects and arrays
const anotherReactiveVariable = reactive(externalVariable);

// Access directly
console.log(anotherReactiveVariable);
Enter fullscreen mode Exit fullscreen mode

If you're still on Vue 2 (as many of us are) you can use observable instead of reactive to achieve exactly the same result.

12. Destructuring in a v-for

Did you know that you can destructure in a v-for?

<li
  v-for="{ name, id } in users"
  :key="id"
>
  {{ name }}
</li>
Enter fullscreen mode Exit fullscreen mode

It's more widely known that you can grab the index out of the v-for by using a tuple like this:

<li v-for="(movie, index) in [
  'Lion King',
  'Frozen',
  'The Princess Bride'
]">
  {{ index + 1 }} - {{ movie }}
</li>
Enter fullscreen mode Exit fullscreen mode

When using an object you can also grab the key:

<li v-for="(value, key) in {
  name: 'Lion King',
  released: 2019,
  director: 'Jon Favreau',
}">
  {{ key }}: {{ value }}
</li>
Enter fullscreen mode Exit fullscreen mode

It's also possible to combine these two methods, grabbing the key as well as the index of the property:

<li v-for="(value, key, index) in {
  name: 'Lion King',
  released: 2019,
  director: 'Jon Favreau',
}">
  #{{ index + 1 }}. {{ key }}: {{ value }}
</li>
Enter fullscreen mode Exit fullscreen mode

13. Looping Over a Range in Vue

The v-for directive allows us to loop over an Array, but it also let's us loop over a range:

<template>
  <ul>
    <li v-for="n in 5">Item #{{ n }}</li>
  </ul>
</template>
Enter fullscreen mode Exit fullscreen mode

This will render out:

  • Item #1
  • Item #2
  • Item #3
  • Item #4
  • Item #5

When we use v-for with a range, it will start at 1 and end on the number we specify.

14. Watch anything in your component

It took me a very long time to realize this, but anything in your component that is reactive can be watched:

export default {
  computed: {
    someComputedProperty() {
      // Update the computed prop
    },
  },
  watch: {
    someComputedProperty() {
      // Do something when the computed prop is updated
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

You can watch:

If you're using the composition API, any value can be watched, as long as it's a ref or reactive object.

15. Stealing Prop Types

Often I find that I'm copying prop types from a child component, just to use them in a parent component. But I've discovered that stealing those prop types is much better than just copying them.

For example, we have an Icon component being used in this component:

<template>
  <div>
    <h2>{{ heading }}</h2>
    <Icon
      :type="iconType"
      :size="iconSize"
      :colour="iconColour"
    />
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

To get this to work, we need to add the correct prop types, copying from the Icon component:

import Icon from './Icon';
export default {
  components: { Icon },
  props: {
    iconType: {
      type: String,
      required: true,
    },
    iconSize: {
      type: String,
      default: 'medium',
      validator: size => [
        'small',
        'medium',
        'large',
        'x-large'
      ].includes(size),
    },
    iconColour: {
      type: String,
      default: 'black',
    },
    heading: {
      type: String,
      required: true,
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

What a pain.

And when the prop types of the Icon component are updated, you can be sure that you'll forget to come back to this component and update them. Over time bugs will be introduced as the prop types for this component start to drift away from the prop types in the Icon component.

So that's why we'll steal them instead:

import Icon from './Icon';
export default {
  components: { Icon },
  props: {
    ...Icon.props,
    heading: {
      type: String,
      required: true,
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

It doesn't have to get any more complicated than that!

Except in our example, we have "icon" added to the beginning of each prop name. So we'll have to do some extra work to get that to happen:

import Icon from './Icon';

const iconProps = {};

// Do some processing beforehand
Object.entries(Icon.props).forEach((key, val) => {
  iconProps[`icon${key[0].toUpperCase()}${key.substring(1)}`] = val;
});

export default {
  components: { Icon },
  props: {
    ...iconProps,
    heading: {
      type: String,
      required: true,
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Now, if the prop types in the Icon component are modified, our component will stay up-to-date.

But what if a prop type is added or removed from the Icon component? To cover those cases we can use v-bind and a computed prop to keep things dynamic.

All of these were first published in my weekly newsletter. Make sure to sign up if you want more great tips like these!

16. Detecting clicks outside of an element (or inside)

Once in awhile I need to detect whether a click happens inside or outside of a particular element el. This is the approach I typically use:

window.addEventListener('mousedown', e => {
  // Get the element that was clicked
  const clickedEl = e.target;

  // `el` is the element you're detecting clicks outside of
  if (el.contains(clickedEl)) {
    // Clicked inside of `el`
  } else {
    // Clicked outside of `el`
  }
});
Enter fullscreen mode Exit fullscreen mode

17. Recursive slots

One time I decided to see if I could make a v-for component using only the template. Along the way I discovered how to use slots recursively, too.

This is what the component looks like:

<!-- VFor.vue -->
<template>
    <div>
        <!-- Render the first item -->
    {{ list[0] }}
        <!-- If we have more items, continue!
                 But leave off the item we just rendered -->
    <v-for
      v-if="list.length > 1"
            :list="list.slice(1)"
        />
    </div>
</template>
Enter fullscreen mode Exit fullscreen mode

If you wanted to do this with scoped slots — and why wouldn't you?! — it just takes a few tweaks:

<template>
  <div>
    <!-- Pass the item into the slot to be rendered -->
    <slot v-bind:item="list[0]">
      <!-- Default -->
      {{ list[0] }}
    </slot>

    <v-for
      v-if="list.length > 1"
      :list="list.slice(1)"
    >
      <!-- Recursively pass down scoped slot -->
      <template v-slot="{ item }">
        <slot v-bind:item="item" />
      </template>
    </v-for>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Here is how this component is used:

<template>
  <div>
    <!-- Regular list -->
    <v-for :list="list" />

    <!-- List with bolded items -->
    <v-for :list="list">
      <template v-slot="{ item }">
        <strong>{{ item }}</strong>
      </template>
    </v-for>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

For a more detailed explanation of this example and nested slots, check out my blog post on it: How to Use Nested Slots in Vue (including scoped slots)

18. Component Metadata

Not every bit of info you add to a component is state. Sometimes you need to add some metadata that gives other components more information.

For example, if you're building a bunch of different widgets for an analytics dashboard like Google Analytics:

Screenshot of Google Analytics dashboard

If you want the layout to know how many columns each widget should take up, you can add that directly on the component as metadata:

export default {
  name: 'LiveUsersWidget',
  // 👇 Just add it as an extra property
  columns: 3,
  props: {
    // ...
  },
  data() {
    return {
      //...
    };
  },
};
Enter fullscreen mode Exit fullscreen mode

You'll find this metadata as a property on the component:

import LiveUsersWidget from './LiveUsersWidget.vue';
const { columns } = LiveUsersWidget;
Enter fullscreen mode Exit fullscreen mode

You can also access the metadata from within the component through the special $options property:

export default {
  name: 'LiveUsersWidget',
  columns: 3,
  created() {
    // 👇 `$options` contains all the metadata for a component
    console.log(`Using ${this.$options.metadata} columns`);
  },
};
Enter fullscreen mode Exit fullscreen mode

Just keep in mind that this metadata is the same for each instance of the component, and is not reactive.

Other uses for this include (but are not limited to):

  • Keeping version numbers for individual components
  • Custom flags for build tools to treat components differently
  • Adding custom features to components beyond computed props, data, watchers, etc.
  • and many more I can't think of!

See a live example here: https://codesandbox.io/s/vue-metadata-bew9j?file=/src/App.vue

19. Multi-file single-file components

Here's a little known feature of SFC.

You can import files just like you would with a regular HTML file:

<!-- A "single" file component -->
<template src="./template.html"></template>
<script src="./script.js"></script>
<style scoped src="./styles.css"></style>
Enter fullscreen mode Exit fullscreen mode

If you need to share styles, docs, or anything else, this can come in really handy. Also perfect for that super long component file that's wearing out your finger from all the scrolling...

Here's a working demo of it in action: https://codesandbox.io/s/interesting-rosalind-9wwmr?file=/src/components/HelloWorld.vue

20. Reusable Components Aren't What You Think

Reusable components don't have to be big or complex things.

I often make small and short components reusable.

Because I'm not re-writing this code all over the place, updating it becomes much easier, and I can make sure that every OverflowMenu looks and works exactly the same — because they are the same!

<!-- OverflowMenu.vue -->
<template>
  <Menu>
    <!-- Add a custom button to trigger our Menu -->
    <template #button v-slot="bind">
      <!-- Use bind to pass click handlers,
           a11y attributes, etc. -->
      <Button v-bind="bind">
        <!-- Use our own "..." icon and no text
             for this button -->
        <template #icon>
          <svg src="./ellipsis.svg" />
        </template>
      </Button>
    </template>
  </Menu>
</template>
Enter fullscreen mode Exit fullscreen mode

Here we're taking a Menu component, but adding a '...' (ellipsis) icon to the button that triggers it open.

It almost seems like it's not worth making a reusable component out of this, because it's only a few lines. Can't we just add the icon every time we want to use a Menu like this?

But this OverflowMenu will be used dozens of times, and now if we want to update the icon or it's behaviour, we can do it very easily. And using it is much simpler too!

<template>
  <OverflowMenu
    :menu-items="items"
    @click="handleMenuClick"
  />
</template>
Enter fullscreen mode Exit fullscreen mode

If you want to go even deeper on building highly reusable components, I have a course that teaches you a completely different way of thinking about your components.

21. Calling a Method from Outside of the Component

You can call a method from outside of a component by giving it a ref:

<!-- Parent.vue -->
<template>
  <ChildComponent ref="child" />
</template>
Enter fullscreen mode Exit fullscreen mode
// Somewhere in Parent.vue
this.$refs.child.method();
Enter fullscreen mode Exit fullscreen mode

Let me explain this one a bit more.

Every now and then the "best practices" don't work for what you're doing, and you need an escape hatch like this.

Typically, we communicate between components using props and events. Props are sent down into child components, and events are emitted back up to parent components.

<template>
  <ChildComponent
    :tell-me-what-to-do="someInstructions"
    @something-happened="hereIWillHelpYouWithThat"
  />
</template>
Enter fullscreen mode Exit fullscreen mode

Once in a while though, you may end up in a situation where you need your parent to trigger a method in the child component. This is where only passing props down doesn't work as well.

You could pass a boolean down and have the child component watch it:

<!-- Parent.vue -->
<template>
  <ChildComponent :trigger="shouldCallMethod" />
</template>
Enter fullscreen mode Exit fullscreen mode
// Child.vue
export default {
  props: ['trigger'],
  watch: {
    shouldCallMethod(newVal) {
      if (newVal) {
        // Call the method when the trigger is set to `true`
        this.method();
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This works fine, but only on the first call. If you needed to trigger this multiple times you'd have to clean up and reset the state. The logic would then look like this:

  1. The Parent component passes true to trigger prop
  2. Watch is triggered, and the Child component calls the method
  3. The Child component emits an event to tell the Parent component that the method has been triggered successfully
  4. The Parent component resets trigger back to false, so we can do this all over again

Ugh.

Instead, if we set a ref on the child component we can call that method directly:

<!-- Parent.vue -->
<template>
  <ChildComponent ref="child" />
</template>
Enter fullscreen mode Exit fullscreen mode
// Somewhere in Parent.vue
this.$refs.child.method();
Enter fullscreen mode Exit fullscreen mode

Yes, we're breaking the "props down, events up" rule, and we're breaking encapsulation, but it's so much cleaner and easier to understand that it's worth it!

Sometimes the "best" solution ends up being the worst solution.

22. Watching Arrays and Objects

The trickiest part of using a watcher is that sometimes it doesn't seem to trigger properly.

Usually, this is because you're trying to watch an Array or an Object, but didn't set deep to true:

export default {
  name: 'ColourChange',
  props: {
    colours: {
      type: Array,
      required: true,
    },
  },
  watch: {
    // Use the object syntax instead of just a method
    colours: {
      // This will let Vue know to look inside the array
      deep: true,

      // We have to move our method to a handler field
      handler()
        console.log('The list of colours has changed!');
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Using the reactivity API from Vue 3 would look like this:

watch(
  colours,
  () => {
    console.log('The list of colours has changed!');
  },
  {
    deep: true,
  }
);
Enter fullscreen mode Exit fullscreen mode

Here are the docs for Vue 3 and Vue 2 if you want to read more on this.

23. Deep Linking with Vue Router

You can store (a bit of) state in the URL, allowing you to jump right into a specific state on the page.

For example, you can load a page with a date range filter already selected:

someurl.com/edit?date-range=last-week
Enter fullscreen mode Exit fullscreen mode

This is great for the parts of your app where users may share lots of links, for an app that is server rendered, or communicating more information between two separate apps than a regular link normally provides.

You can store filters, search values, whether a modal is open or closed, or where in a list we've scrolled to — perfect for infinite pagination.

Grabbing the query using vue-router works like this (this will work on most Vue frameworks like Nuxt and Vuepress too):

const dateRange = this.$route.query.dateRange;
Enter fullscreen mode Exit fullscreen mode

To change it we use the RouterLink component and update the query:

<RouterLink :to="{
  query: {
    dateRange: newDateRange
  }
}">
Enter fullscreen mode Exit fullscreen mode

Here's a demo of this in action:

https://codesandbox.io/s/deep-linking-with-vue-router-vhxkq?file=/src/components/DeepLinking.vue

24. Another Use for the Template Tag

The template tag can be used anywhere inside of your template to organize code better.

I like to use it to simplify v-if logic, and sometimes v-for, too.

In this example, we have several elements that all use the same v-if condition:

<template>
  <div class="card">
    <img src="imgPath" />
    <h3>
      {{ title }}
    </h3>
    <h4 v-if="expanded">
      {{ subheading }}
    </h4>
    <div
      v-if="expanded"
      class="card-content"
    >
      <slot />
    </div>
    <SocialShare v-if="expanded" />
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

It's a little clunky, and not obvious at first that a bunch of these elements are being shown and hidden together. On a bigger, more complicated component, this could be an even worse situation!

But we can fix that.

We can use the template tag to group these elements, and lift the v-if on to the template tag itself:

<template>
  <div class="card">
    <img src="imgPath" />
    <h3>
      {{ title }}
    </h3>
    <template v-if="expanded">
      <h4>
        {{ subheading }}
      </h4>
      <div class="card-content">
        <slot />
      </div>
      <SocialShare />
    </template>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Now we have something that's much easier to read. And it's much easier to understand what's going on at a glance.

25. A better way to handle errors (and warnings)

You can provide a custom handler for errors and warnings in Vue:

// Vue 3
const app = createApp(App);
app.config.errorHandler = (err) => {
  alert(err);
};

// Vue 2
Vue.config.errorHandler = (err) => {
  alert(err);
};
Enter fullscreen mode Exit fullscreen mode

Bug tracking services like Bugsnag and Rollbar hook into these handlers to log errors, but you can also use them to handle errors more gracefully for a better UX.

For example, instead of the application just crashing if an error is unhandled, you can show a full page error screen and get the user to refresh or try something else.

In Vue 3 the error handler only works on template and watcher errors, but the Vue 2 error handler will catch almost everything. The warning handler in both versions only works in development.

I created a demo showing how this works. It uses Vue 3, but Vue 2 works nearly the same:

Error Handler Demo

Exclusive tips and insights every week

Join 8135 other Vue devs and get exclusive tips and insights like these delivered straight to your inbox, every week.

You have great content in your emails. I seriously learn something from every one of them. — Titus Decali

Thanks for another beautiful tip 🙏 — Victor Onuoha

Loving these, and the spaced repetition — Mark Goldstein

Sign up here

Top comments (36)

Collapse
 
jvdl profile image
John van der Loo

One of your code samples isn't quite correct for tip #15. You've got:

// Do some processing beforehand
Object.entries(Icon.props).forEach((key, val) => {
  iconProps[`icon${key.toUpperCase()}`] = val;
});
Enter fullscreen mode Exit fullscreen mode

Where it should be:

// Do some processing beforehand
Object.entries(Icon.props).forEach(([key, val]) => {
  iconProps[`icon${key[0].toUpperCase() + key.substring(1)}`] = val;
});
Enter fullscreen mode Exit fullscreen mode

That tip is solid advice btw, I'm still discovering how to do all these things in a Vue.js world and apply all my React based knowledge in a useful way :-)

Re. tip #15, have you considered creating a file with prop types in it? This is something I've done in previous projects, particularly useful if you have the same/similar complex types used in multiple places. You could for example make the icon it's own prop type so that rather than passing down many attributes, you pass down a single object as an attribute:
e.g.
prop-types.js:

export const iconPropType = {
  type: {
    type: String,
    required: true
  },
  size: sizePropType,
  colour: {
    type: String,
    default: "#000"
  }
};
export const sizePropType = {
  type: String,
  default: "medium",
  validator: (size) => ["small", "medium", "large", "x-large"].includes(size)
};
Enter fullscreen mode Exit fullscreen mode

Icon.vue script:

import { iconPropType } from "./prop-types";
export default {
  props: {
    ...iconPropType,
  },
}
Enter fullscreen mode Exit fullscreen mode

Example usage:

import Icon from "./Icon";
import { iconPropType } from "./prop-types";

export default {
  components: { Icon },
  props: { icon: iconPropType },
};
Enter fullscreen mode Exit fullscreen mode

The icon can then be used as <Icon v-bind="icon" />

Collapse
 
michaelthiessen profile image
Michael Thiessen

Thanks for pointing this out! Fixed it in the article.

I haven't thought of using a separate file, but I think that would be very useful if you're using the same set of props a lot.

Vue prop types also allow any object with a constructor as a valid type, so you can create your own custom prop types that way. I've never experimented with that, but I'm sure there's a good use case for it.

Collapse
 
jvdl profile image
John van der Loo

Good to know about the alternate ways to use prop types, I hadn't considered using a constructable object, certainly that would be useful if you were passing around instances of said items rather than keeping a class/function in sync with a prop type definition.

In terms of using a separate file, indeed very useful for prop types that are used in multiple places (think Users, Avatars, Posts, etc.).

Collapse
 
michaelthiessen profile image
Michael Thiessen

Which tip is your favourite?

Collapse
 
nasirdoe profile image
Muhamad Nasir

The 25, i never use before. Thanks man

Collapse
 
michaelthiessen profile image
Michael Thiessen

No problem!

Collapse
 
theme_selection profile image
ThemeSelection

Superb..!! Very coprehensive and directive.

In case any one is starting withVuejs project here is an open source vuejs admin template. Materio Free Vuetify VueJS Admin Template>

Collapse
 
michaelthiessen profile image
Michael Thiessen

Thanks for sharing this!

Collapse
 
theme_selection profile image
ThemeSelection

You are welcome!

Collapse
 
royalfig profile image
Ryan Feigenbaum

Incredible. Ty!

Collapse
 
michaelthiessen profile image
Michael Thiessen

You are very welcome 😊

Collapse
 
drumstix42 profile image
Mark

Note that for preprocessors you can use ::v-deep selector.

Collapse
 
michaelthiessen profile image
Michael Thiessen

Yes, you can use >>>, ::v-deep, or /deep/. They all work the same, but some pre-processors don't like >>>.

vue-loader.vuejs.org/guide/scoped-...

Collapse
 
nove1398 profile image
nove1398

This is great, i started vue and these helped me out quite a bit

Collapse
 
michaelthiessen profile image
Michael Thiessen

Which tip was most interesting to you?

Collapse
 
nove1398 profile image
nove1398

state sharing

Collapse
 
webpig profile image
朱宝华

Hi, Thanks, the article is very good!
I have a question:
tip 15: But what if a prop type is added or removed from the Icon component? To cover those cases we can use v-bind and a computed prop to keep things dynamic.
What does this mean? Can you help me? Thanks.

Collapse
 
webpig profile image
朱宝华 • Edited

mean: <Icon v-bind="iconProps" /> ?

Collapse
 
treboryx profile image
Robert

This post is amazing.

Collapse
 
michaelthiessen profile image
Michael Thiessen

Glad you liked it 😊

Collapse
 
ozzythegiant profile image
Oziel Perez

Holy cow, this content is worthy of a book! Thanks! Now I have something to study and master Vue as I've made it my go-to framework for front end development

Collapse
 
michaelthiessen profile image
Michael Thiessen

Thanks! Vue is definitely a great choice of framework 👌

Collapse
 
webdev710 profile image
webdev-710

Quite a lot new to me.
Surely make use of them

Thanks

Collapse
 
michaelthiessen profile image
Michael Thiessen

Glad I could help!

Collapse
 
qq449245884 profile image
qq449245884

Dear Michael Thiesseni,may I translate your article into Chinese?I would like to share it with more developers in China. I will give the original author and original source.

Collapse
 
michaelthiessen profile image
Michael Thiessen

Absolutely! That would be incredible.

Collapse
 
qq449245884 profile image
qq449245884

Thank you very much!

Collapse
 
geminii profile image
Jimmy

Awesome post 👌
Learn many things again about VueJs 🚀

Collapse
 
michaelthiessen profile image
Michael Thiessen

What was one of the things you learned from the post?

Collapse
 
geminii profile image
Jimmy

Shorthand for single scoped slot / and errors handling. Awesome tips thanks a lot again 😀

Collapse
 
rauschmerscen profile image
Rausch Merscen

Nice

Collapse
 
mat3uszkek profile image
Mateusz Skrobiś

Thats one great article, thank you! :)

Collapse
 
rogierstegeman profile image
Rogier Stegeman

Finally a list with useful tips that you don't already know after your first app!