When handling form inputs in Vue, we usually use v-model
to achieve two-way binding. But if we want to put form data into Vuex store, two-way binding becomes a problem, since in strict mode, Vuex doesn’t allow state change outside mutation handlers. Take the following snippet for instance, while full code can be found on GitHub (link).
src/store/table.js
1 | export default { |
src/components/NonStrict.vue
1 | <b-form-group label="Table Name:"> |
When we input something in “Table Name” field, an error will be thrown in browser’s console:
1 | Error: [vuex] Do not mutate vuex store state outside mutation handlers. |
Apart from not using strict mode at all, which is fine if you’re ready to lose some benefits of tracking every mutation to the store, there’re several ways to solve this error. In this article, we’ll explore these solutions, and explain how they work.
Local Copy
The first solution is to copy the form data from Vuex store to local state, do normal two-way binding, and commit to store when user submits the form.
src/components/LocalCopy.vue
1 | <b-form-input v-model="table.table_name" /> |
src/store/table.js
1 | export default { |
There’re two caveats in this solution. One is when you try to update the form after committing to store, you’ll again get “Error: [vuex] Do not mutate vuex store state outside mutation handlers.” It’s because the component’s local copy is assigned into Vuex store. We can modify the setTable
mutation to solve it.
1 | setTable (state, payload) { |
Another problem is when other components commit changes to Vuex store’s table
, e.g. in a dialog with sub-forms, current component will not be updated. In this case, we’ll need to set a watched property.
1 | <script> |
This approach can also bypass the first caveat, because following updates in component’s form will not affect the object inside Vuex store.
Explicit Update
A ReactJS-like approach is to commit data on input / change event, i.e. use one-way data binding instead of two-way, and let Vuex store become the single source of truth of your application.
src/components/ExplicitUpdate.vue
1 | <b-form-input :value="table.table_name" @input="updateTableForm({ table_name: $event })" /> |
src/store/table.js
1 | export table { |
This is also the recommended way of form handling in Vuex doc, and according to Vue’s doc, v-model
is essentially a syntax sugar for updating data on user input events.
Computed Property
Vue’s computed property supports getter and setter, we can use it as a bridge between Vuex store and component. One limitation is computed property doesn’t support nested property, so we need to make aliases for nested states.
src/components/ComputedProperty.vue
1 | <b-form-input v-model="tableName" /> |
When there’re a lot of fields, it becomes quite verbose to list them all. We may create some utilities for this purpose. First, in Vuex store, we add a common mutation that can set arbitrary state indicated by a lodash-style path.
1 | mutations: { |
Then in component, we write a function that takes alias / path pairs, and creates getter / setter for them.
1 | const mapFields = (namespace, fields) => { |
In fact, someone’s already created a project named vuex-map-fields, whose mapFields
utility does exactly the same thing.