Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More elegant two way data binding - Form Handling with computed property that returns an Object #936

Closed
tjunussov opened this issue Sep 6, 2017 · 9 comments

Comments

@tjunussov
Copy link

What problem does this feature solve?

I am using vue-idb ( wrapper for Dixie.js IndexedDB ), with predefined getters, mutations, actions,
if i use Object with properties in form, vuex throws error "Do not mutate vuex store state outside mutation handlers" so according documentation i need to write for every computed property get/set methods

if i have big form with bunch of inputs, i have to write a lot of setters and getters?

Here is example i have to write in order not to have errors

html pug template

div(v-for="c in contactsStore",:contact="c",@click="contactsSelect(c)")
form
    input.form-control(placeholder="Add Name",v-model="selected_name")
    input.form-control(placeholder="Add phone",v-model="selected_code")
    textarea.form-control(placeholder="Add note",v-model="selected_note")

js

import { mapGetters, mapActions } from 'vuex'

export default {
  name: 'contacts',
  computed: {
      selected_name: {
           get:function(){ this.$store.getters.getContactsSelected.name  }
           set(value) {  this.$store.commit('updateContact_name', value)   },
      },
     selected_code: {
           get:function(){ this.$store.getters.getContactsSelected.code  }
           set(value) {  this.$store.commit('updateContact_code', value)   },
      },
      selected_note: {
           get:function(){ this.$store.getters.getContactsSelected.note  }
           set(value) {  this.$store.commit('updateContact_note', value)   },
    })

What does the proposed API look like?

Example which gives error, because i directly reference selected propery name, code, note

html pug template

vcard(v-for="c in contactsStore",:contact="c",@remove="remove",@add="add()",@click="contactsSelect(c)")
form
    input.form-control(placeholder="Add Name",v-model="selected.name")
    input.form-control(placeholder="Add phone",v-model="selected.code")
    textarea.form-control(placeholder="Add note",v-model="selected.note")

js

import { mapGetters, mapActions } from 'vuex'

export default {
  name: 'contacts',
  computed: {
    ...mapGetters({ 
      selected: 'getContactsSelected'
    }),
   methods: {
    ...mapActions(['contactsSelect']),
  }

or are there any other best aproaches ?
p.s maybe clone Object, work with them and after change commit change ?

@tjunussov
Copy link
Author

Here is one proposal found
https://ypereirareis.github.io/blog/2017/04/25/vuejs-two-way-data-binding-state-management-vuex-strict-mode/

@ktsn
Copy link
Member

ktsn commented Sep 10, 2017

This looks a question rather than proposal. Please use our forum or chat room to ask questions.


For your problem, I think you should not include whole form data in Vuex store, you should stated them in your component local data instead. To my experience, such form data will not need to be referred from other components.

If you really need to place them in store, I would suggest to create a helper for generating two-way computed object like Vuex's mapXXX helpers.

Or you also can create general update mutation for form data:

// store module
mutations: {
  update(state, { path, value }) {
    const parent = path.slice(-1).reduce((state, key) => {
      return state[key]
    }, state)
    parent[path[path.length - 1]] = value
  }
}
<template lang="pug">
    input(placeholder="Add Name", :value="data.name", @input="update('name', $event)")
    input(placeholder="Add phone", :value="data.code", @input="update('code', $event)")
    textarea(placeholder="Add note", :value="data.note", @input="update('note', $event)")
</template>

<script>
export default {
  computed: {
    data () {
      return this.$store.state.yourFormData
    }
  },
  methods: {
    update (key, event) {
      this.$store.commit('update', {
        path: [key],
        value: event.target.value
      })
    }
  }
}
</script>

@ktsn ktsn closed this as completed Sep 10, 2017
@tjunussov
Copy link
Author

Thanks for example,
I though it would be proposal to update manual, with such cases like mine ( in real industrial world : )

@marceloavf
Copy link

@ktsn @tjunussov If you have many steps in a form, would you suggest to do it with Custom Events? Wouldn't be a great idea to use Vuex?

@leodutra
Copy link

leodutra commented Feb 20, 2018

import { mapState } from 'vuex'

// based on article: 
// VueJS - Two way data binding and state management with Vuex and strict mode
// https://ypereirareis.github.io/blog/2017/04/25/vuejs-two-way-data-binding-state-management-vuex-strict-mode/

export default function mapStatesTwoWay (namespace, states, updateCb) {
    const mappedStates = mapState(namespace, states)
    const res = {}
    for (const key in mappedStates) {
        res[key] = {
            set (value) {
                updateCb.call(this, { [key]: value })
            },
            get () {
                return mappedStates[key].call(this)
            }
        }
    }
    return res
}

Sample usage:

computed: {
  ...mapStatesTwoWay('assinatura', {
    nomeCliente: state => state.current.nomeCliente,
    email: state => state.current.email,
    telefone: state => state.current.telefone,
    codigoPessoa: state => state.current.codigoPessoa
  }, function (stateUpdate) {
    // ex: { nomeCliente: 'John' }  
    this.updateCurrent(stateUpdate)
  })
},
methods: {
  ...mapMutations('assinatura', ['updateCurrent'])
}
state: {
  current: {
     nomeCliente: '',
     email: '',
     telefone: '',
     codigoPessoa: ''
  }
}
mutations: {
  updateCurrent(state, payload) {
    Object.assing(state.current, payload) // or lodash.merge() for deep changes
  }
}

If necessary, you should handle the stateUpdate for right mutation / different data update.
This uses mapState and returns computed {get,set}, under the hood... for consistency and v-model compatibility.

I hope this can derive in a more lean solution.

GIST

@maoberlehner
Copy link

I made a little tool which can help with handling forms with Vuex: vuex-map-fields

More infos:

@tjunussov
Copy link
Author

@maoberlehner great! I think we need something like this in VUEX natively in future :)

@Shyam-Chen
Copy link

I made a simple library.

https://github.com/Vanilla-IceCream/vuex-bound

@yarsky-tgz
Copy link

I have own solution proposition. I've need a solution for similar problem, i've read vuex-bound and vuex-map-fields sources both, but was not satisfied and decided to write own:

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants