Creating a transparent base component with Vue.js

Base components in Vue.js are components, which can help you reducing the amount of work you have to do and code you have to write in order to (re)use components in Vue.js in multiple places.

Creating a transparent base component with Vue.js

Base components in Vue.js are components, which can help you reducing the amount of work you have to do and code you have to write in order to (re)use components in Vue.js in multiple places. They also help you changing the markup easily, as you only have a few central places where you need to do so. An example could be a form field. You usually want to have a label along with an input field (or select, radio button, etc) but you would also like to avoid to repeat the same code over and over again for multiple input fields. One possible way of building and using such a base component is what this article is going to show you.

If you want to see the base component we're going to build in action, I've created a CodeSanbox, which you can use to try out your ideas/use cases:

Edit Vue.js Base Component

Basics first

To build this base component, we're going to use Vue's v-model directive. To recap, here's a simple example on how to use v-model:

export default {
  data() {
    return {
      username: ''
    }
  }
}
<input type="text" v-model="username">

In our Vue.js instance, we have the data property username and in our template we bind that variable to the input field. All the v-model directive really does, is to bind the value of username to the value property of the input field (and therefor keep the content of that input field always in sync with the variable) and update the username variable with the new value whenever someone types something into the input field.

We can use this directive also for our base component, but we first need to understand, how v-model works under the hood. By default, v-model binds the variable you pass to the directive to the value property of the element/component and whenever that element/component emits an input event, the variable you passed to the directive will be update with the new value automatically.

This means, that the v-model directive in this example behaves equivalently to v-bind:value="username" and v-on:input="username = $event". And this means, that in our base component we can expect to get a prop named value and emit an input event back to the parent with the new value when we want to update the value in the parent component.

v-bind and v-on shorthands

In the following examples we're going to use the shorthand-syntax for the v-bind and v-on directives:

v-bind:  becomes :
v-on:  becomes @

All this means is, that instead of v-bind:value="username" we can use :value="username" and instead of v-on:input="username = $event" we can use @input="username = $event".

You can read more about this shorthand syntax in the official Vue.js documentation.

Building the base component

Now that we know how the v-model directive works under the hood, let's actually build the base component for an input field:

export default {
  name: "BaseInput",
  props: {
    label: String,
    type: String,
    placeholder: String
  }
}
<label>
  {{ label }}
  <input
    :type="type"
    :placeholder="placeholder"
    @input="$emit('input', $event.target.value)"
  >
</label>

In the above BaseInput we're expecting a prop for the label, the input type, a placeholder text and the actual value. We can use all those props inside the template and as soon as the input field emits the input event, we can listen to it and emit another input event back to the parent component in order to let the v-model directive update the variable accordingly. This way, our BaseInput component is a transparent component, which can be used with v-model without caring too much about how it actually works internally.

Using our BaseInput component

Now in our parent component, we can use this BaseInput component in the following way (we assume that the component was registered globally and we therefor do not need to register the component locally again):

import BaseInput from "./components/BaseInput";

export default {
  name: "App",
  components: {
    BaseInput
  },
  data() {
    return {
      username: "",
      password: ""
    };
  }
};
<BaseInput label="Username" type="text" placeholder="Username" v-model="username"/>
<BaseInput label="Password" type="password" placeholder="********" v-model="password"/>

Further use cases

Now this of course is just a very basic example on how you can use base components, because you could of course customize it further, build renderless components or partially renderless components, integrate slots etc.

If you have any further questions, aspects you are unsure with or found anything, that is incorrect in this article, don't hesitate to leave a comment below or reach out to me on Twitter: @ivansieder