V-model support without requiring value prop
Tags: vue, tip
Published: 2021-01-22 09:08:17

Have you ever wished to create a component that supports `v-model` directive, but works without it as well? First things first. If you've tried Vue.js you've probably learned that you can bind variables on inputs. This creates a two-way data binding which syncs the variable and the input's state. All you need to do is to use the v-model directive.

You may also have learned that you can use this directive with any custom component since `v-model` is just a syntax sugar to cover both ways of the data binding.  You can learn more about this [here](https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components). Hence
```html
<input v-model="searchText">
```

turns into 

```html
<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>
```

As you can see, in order to implement the support, you have to declare a prop variable called _value_ and emit an event labeled _input_. And that's it.


However, you will quickly find out that at this point the component indeed supports the `v-model` directive, but it doesn't work at all without it. That's often undesirable. For instance, imagine you'd like to create a custom search component that includes a text input. Since it's a mere extension of a text input, it's reasonable that it should support `v-model`. But it is also reasonable that you'd like to be able to use it without it since the input inside would normally work straight away had it been a plain HTML element. Let's tackle this.

## Optional v-model support
Let's start by creating a simple search component that will accept `value` as prop. If the user doesn't provide it, it's initiated to an empty value. 

```vue
  props: {
    value: {
      type: String,
      default: "",
    },
  },
```

However, we can't use this prop directly in the input since that would mutate it which is not recommended. To circumvent this problem we'll create a clever computed value that will use the value prop if passed from the parent, or a custom local value otherwise. We'll make use of the extended computed property syntax where one can declare different functions for setter and getter of the computed function.

```vue
  data() {
    return {
      localValue: this.value,
    };
  },
  computed: {
    searchValue: {
      get() {
        return this.isValuePropSet() ? this.value : this.localValue;
      },
      set(value) {
        this.$emit("input", value);
        this.localValue = value;
      },
    },
  },
  methods: {
    isValuePropSet() {
      return (
        !!this.$options.propsData && this.$options.propsData.value !== undefined
      );
    },
  },
```

Let's first take a look at the getter. When retrieving the value, the `isValuePropSet()` method is invoked. This method returns true when the `value` prop was set by the parent, not initialized to empty string by the default property. So when it was set from the outside, we'll just return the _value_ property and the component works as if it was implemented as a regular component with `v-model` support. However, when the value was not set, then the getter returns `localValue` instead. In the setter the current value is both emitted as an input event and stored in the `localValue`. 

With this pattern, we can bind the clever `searchValue` computed property to the input as usual

```html
<input v-model="searchValue" />
```

And that's it. The search component works with `v-model` attached as well as without it. Check out the example [sandbox](https://codesandbox.io/s/optional-v-model-component-q3g4p) to see it wholly in action.