Sean D
Sean D

Reputation: 4292

2 way data binding with Vuex

In the code below I'm 2 way binding the output of a textarea into a p element, once from the component's internal state and once from Vuex. The Vuex state does show the initial value, but the value doesn't update as I add or delete text (as it does correctly with the 1st textarea bound to the internal data). What is the difference that is causing this issue?

Component code:

<template>
  <div>
    <div>
      <textarea name="textarea1" id="txtid" cols="40" rows="30" v-model="internal_state"></textarea>
      <p> {{ internal_state }}</p>
      <hr>

      <textarea name="textarea1" id="txtid" cols="40" rows="30" v-model="this.$store.state.vuex_state"></textarea>
      <p> {{ this.$store.state.vuex_state }}</p>
      <hr>

    </div>
  </div>
</template>

<script>

  export default {
    name: 'WriteArea',
    data () {
      return {
        internal_state: ''
      }
    },
    methods: {


    }
  }

</script>

Vuex code:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export const store = new Vuex.Store({
  strict: true,
  state: {
    counter: 0,
    vuex_state: 'starting string'
  },
  getters: {
    vuex_getter1: (state) => {
      return state.vuex_string
    }
  }
})

Upvotes: 7

Views: 10606

Answers (3)

Kareem Dabbeet
Kareem Dabbeet

Reputation: 3994

Using: vuex-map-fields

from vuex-map-fields repo:

Enable two-way data binding for form fields saved in a Vuex store.

Vuex Store

import Vue from 'vue'
import Vuex from 'vuex'
import { getField, updateField } from 'vuex-map-fields';

Vue.use(Vuex)

export const store = new Vuex.Store({
  strict: true,
  state: {
    counter: 0,
    vuex_state: 'starting string'
  },
  getters: {
    getField, //  Add the `getField` getter to the `getters` of your Vuex store.
    vuex_getter1: (state) => {
      return state.vuex_string
    }
  }

  mutations: {
    updateField,  // Add the `getField` getter to the `getters` of your Vuex store.
  }
})

Component code:

<template>
  <div>
    <div>
      <textarea name="textarea1" id="txtid" cols="40" rows="30" v-model="internal_state"></textarea>
      <p> {{ internal_state }}</p>
      <hr>

      <textarea name="textarea1" id="txtid" cols="40" rows="30" v-model="vuex_state"></textarea>
      <p> {{ vuex_state }}</p>
      <hr>

    </div>
  </div>
</template>

<script>
import { mapFields } from 'vuex-map-fields';

export default {
  name: 'WriteArea',
  data () {
    return {
      internal_state: ''
    }
  }, 
  computed: {
    // The `mapFields` function takes an array of
    // field names and generates corresponding
    // computed properties with getter and setter
    // functions for accessing the Vuex store.
    ...mapFields([
      'vuex_state ',
    ]),
  },
}

</script>

Upvotes: 0

Yaroslav Dobzhanskij
Yaroslav Dobzhanskij

Reputation: 562

You can try to use mine library for 2 way binding vuex problem solution

https://github.com/yarsky-tgz/vuex-dot

Example:

<template>
  <input v-model="name"/>
  <input v-model="email"/>
  <button @click.stop="step++">next</button>
</template>

<script>
  import { takeState } from 'vuex-dot';

  export default {
    computed: {
      step: takeState('wizard.step') 
        .commit('setWizardStep')
        .map(),
      ...takeState('user')
        .expose(['name', 'email'])
        .commit('editUser')
        .map()
    }
  }
</script>

store/index.js

export default new Vuex.Store({
  state: {
    wizard: {
      step: 1
    },
    user: {
      name: 'John',
      email: '[email protected]'
    }
  },
  mutations: {
    setWizardStep(state, step) {
      state.wizard.step = step;
    },
    editUser(state, patch) {
      state.user = Object.assign({}, state.user, patch);
    }
  }
});

Upvotes: 2

aprouja1
aprouja1

Reputation: 1810

Vuex state should be updated via a mutation. See the documentation for this exact problem. Solution is not to use v-model, but instead to bind to the :value of the textarea and then have a custom event to mutate the Vuex state on input: https://vuex.vuejs.org/en/forms.html

<input :value="message" @input="updateMessage">
// ...
computed: {
  ...mapState({
    message: state => state.obj.message
  })
},
methods: {
  updateMessage (e) {
    this.$store.commit('updateMessage', e.target.value)
  }
}

The other option is to create a setter and getter in the same computed property:

<input v-model="message">
// ...
computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}

Upvotes: 10

Related Questions