How To Use V-Model With Vuex

Vue.js

09/01/2021


In my previous blog post "Learn Vuex Through A Tutorial", I demonstrated how to implement state management using Vuex. As you surely know, a central theme of Vuex is its unidirectional data flow. This means two-way binding is a no-go! Unfortunately, this means the Vue directive v-model will not work with it out of the box. 😣 "Oh no! What am I gonna do!?". Don't worry, I've got you covered. In this blog post, I'll show you how to use v-model with Vuex.

In case you were wondering, no matter if you're using Vue 3 or Vue 2, you'll encounter the same issue.

Make your choice

There are two ways of tackling this problem: creating a two-way computed property or "modifying", for lack of a better term, the v-model logic to accommodate Vuex. Both of these solutions work in strict 👮‍♂️ mode.

Let me first explain the latter in more detail.

Solution 1: Modifying v-model logic

As you may or may not know, the v-model directive is essentially syntactic sugar. 🍭 It can be rewritten in a more verbose manner using two other directives, as seen below. In this example, the two-way binding happens by calling updateMessage on any input change and updating message with the input value.

HTML
<template>
<input v-model="message" />
<!-- Is the equivalent of -->
<input :value="message" @input="updateMessage">
</template>
<script>
export default {
data() {
return {
message: 'Chunk Bytes is awesome'
}
},
methods: {
/* Behind the scenes */
updateMessage(event) {
this.message = event.target.value
}
}
}
</script>

Now that we know how v-model actually works under the hood, 🚗 we can make some simple changes to the code in order to make it work with Vuex. Let me show you!

HTML
<template>
<input :value="message" @input="updateMessage">
</template>
<script>
export default {
computed: {
/* Use a computed property instead */
message() {
return this.$store.state.message
}
},
methods: {
/* Call a mutation instead */
updateMessage(event) {
this.$store.commit('updateMessage', event.target.value)
}
}
}
</script>

The first change we need to make is using a computed property (or getter) to retrieve the state data. Alright, step one done. ✅ Next, we need to alter this state by calling a mutation through a method function. You may of course dispatch an action and then commit the mutation, if you prefer to do so.

In case you were wondering, here's what the mutation handler would look like in our store.

JAVASCRIPT
const mutations = {
updateMessage(state, payload) {
state.message = payload
}
}

And really, that's all there is to it! 😊

Solution 2: Two-way computed property

"What's the other solution you talked about?". Glad you didn't forget dear reader! As I previously mentioned, we can tackle this issue by constructing a two-way computed property using a getter and a setter (⚠️ getter in this context is not referring to the actual getter function in Vuex).

HTML
<template>
<input v-model="message">
</template>
<script>
export default {
computed: {
message: {
/* By default get() is used */
get() {
return this.$store.state.message
},
/* We add a setter */
set(value) {
this.$store.commit('updateMessage', value)
}
}
}
}
</script>

In this solution, we don’t actually mess around with the v-model directive, but with the property it’s bound to. By default, a computed property is a getter only. 😲 However, we have the option to also add a setter.

Inside of the computed property, we have a custom getter function called get() directly retrieving the state from our store. On the other hand, in order to alter the state, we use a setter called set(). This calls a mutation handler and passes in the input value that we automatically receive.

Review

Pay attention to the structure of both solutions. You'll see that they are both very similar. In the first one, you essentially have two functions, one as a computed property, the other as a method. In the second case, you have the same functions, just named differently and part of a computed property. Hmmm... 🧐

Using a library

It might not come as a surprise to you that many others have come across this problem and evidently tried to come up with a solution of their own. The most popular library at the moment is vuex-map-fields, with over a thousand stars at the time of this writing.

Its documentation is quite well written in my opinion, so I don't think a tutorial will be necessary (unless you ask me of course... 😋). Nonetheless, knowing someone's already made an easy to use solution should not discourage you from knowing the other alternative solutions I presented to you above.

In fact, you'll sometimes run into a problem with the library and might have to resort to the old-fashioned solution, as I had to do once.


WRITTEN BY

Code and stuff