DEV Community

Cover image for Making v-model Model Value Optional in Vue.js
Alex Grozav
Alex Grozav

Posted on • Originally published at grozav.com

Making v-model Model Value Optional in Vue.js

While writing my Vue.js UI Library, Inkline, I had to find a way to make some components work both with and without providing a model value (v-model). While it's not a common scenario, it's something that you'll definitely come across if you're writing a library and you're serious about Developer Experience (DX).

I call them Optionally Controlled Components, because they're supposed to work out of the box without providing a v-model, but will give you complete control over their state if you do provide a v-model.

The Menu Example

One prime example of an Optionally Controlled Component would be a menu that can be opened (expanded) or closed (collapsed). Let's call the component simply MyMenu.

From a Developer Experience perspective, you'll probably want your library user to be able to drop a <my-menu> into their code and start adding collapsible content right away, without having to worry about handling its open or closed state.

Here's what the component would look like without v-model support:

<template>
    <div class="my-menu">
        <button @click="toggleMenu">
            Menu
        </button>
        <menu v-show="open">
            <slot />
        </menu>
    </div>
</template>

<script>
export default {
    name: 'MyMenu',
    data() {
        return {
            open: false
        };
    },
    methods: {
        toggleMenu() {
            this.open = !this.open;
        }
    }
}
</script>
~~~

## The Optional Model Value

So far so good. Let's consider the following scenario: your user wants to be able to open or close the menu from somewhere else. We know we can open and close the menu internally at this point, but how do we allow the library user to optionally control the state? 

There's a future-proof solution I found, that will save you a lot of trouble. Here's what it looks like:

~~~html
<template>
    <div class="my-menu">
        <button @click="toggleMenu">
            Menu
        </button>
        <menu v-show="open">
            <slot />
        </menu>
    </div>
</template>

<script>
export default {
    name: 'MyMenu',
    emits: [
        'update:modelValue'
    ],
    props: {
        modelValue: {
            type: Boolean,
            default: false
        }
    },
    data() {
        return {
            open: this.modelValue
        };
    },
    methods: {
        toggleMenu() {
            this.open = !this.open;
            this.$emit('update:modelValue', this.open);
        }
    },
    watch: {
        modelValue(value) {
            this.open = value;
        }
    }
}
</script>
~~~

Try a basic example out live on [CodeSandbox](https://codesandbox.io/s/optionally-controlled-components-43y0b?file=/src/components/MyMenu.vue).

You can see above that I've added the usual `modelValue` prop to provide `v-model` support in Vue 3, but mainly I've done three things: 
* I'm setting the initial value of our internal `open` state property to be equal to the one provided via `v-model`. This works wonders, because when there's no `v-model` it would be equal to the specified default, `false` in our case. 
* I'm emitting an `update:modelValue` event every time I change the value of `this.open` internally
* I've added a watcher that ensures I'm always keeping the internal `open` value in sync with the incoming external `modelValue` prop.

![Optionally Controlled Component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jgnucpmbptovtoai1b6s.gif)

## Conclusion

Awesome, isn't it? It's important to never forget about Developer Experience. Something as small as this can add up to precious hours of saved development time if done correctly and consistently. 

I hope you learned something interesting today. I'd love to hear how the Optionally Controlled Components pattern helped you out, so feel free to reach out to me. **Happy coding!**

<small>**P.S.** Have you heard that Inkline 3 is coming with Vue 3 support? Read more about it on [GitHub](https://github.com/inkline/inkline/issues/207).</small>




Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
exodes profile image
Exo Des

Hi, that’s a neat trick. But does the watcher necessary? As your if your props changed, your data would be updated. Can I maybe use computed for lesser performance hit?