davestewart / vuex-pathify

Vue / Vuex plugin providing a unified path syntax to Vuex stores
https://davestewart.github.io/vuex-pathify
MIT License
1.37k stars 57 forks source link

Handling arrays properly #19

Closed haexhub closed 6 years ago

haexhub commented 6 years ago

Hi,

I've got one more question about handling arrays. I read your documentation about this, but was not able to get it to work. Could you please give me some advise?

store/index.js

export const state = () => {
  foo: {
    bar: [
      { foobar: 'hello' }
    , { foobar: 'world' }
    ]
  }
}

pages/index.js

<template>
  <div>
    <input v-for = 'item in items' v-model = 'item.foobar'/>
  </div>
</template>

<script>
  computed: {
    items: sync('foo@bar') // values come up, but can't mutate them
  // ...sync('foo@bar.*') // not working
  // items: sync('foo', ['bar']) not working but written in your docs
  }
</script>

You could probably make your documentation more precisly? :smile:

davestewart commented 6 years ago

Hey @haexhub,

Do you mean this bit?

https://davestewart.github.io/vuex-pathify/#/api/paths?id=syntax

image

I specifically didn't support arrays as I originally designed Pathify to work with pre-defined properties, i.e. filters@sort.order as it was designed to easily wire up UI controls.

I figured arrays would be a bit of a rabbit hole with potentially a.b[9999] not working or being as relevant as a.b.someThing.

In your example above, if you coded it using vanilla JavaScript / Vue, how would it work?

Can you supply a working example (Codepen, or CodeSandbox perhaps) with a concrete (i.e. not foobar example) to show how you would use computed properties?

davestewart commented 6 years ago

Also, very happy to make the docs more helpful / relevant where needed!

When we get to the end of this thread I'll be happy to improve things where needed.

haexhub commented 6 years ago

Hey @davestewart

Thank you for your response!

I obviously misread the docs. image

Thought thats what I need. :confused:

Maybe you have an other approach for me. I want to be more generic while creating forms.

store/index.js

export const state = () => {
  foo: {
    bar: { value: 'Hello' }
  , boo: { value: 'World' }
  }
}

pages/index.vue

<template>
  <div>
    <input v-for = 'item in items' v-model = 'item.value'/> // not working of course
  </div>
</template>

<script>
  data () {
    return {
      values: ['bar', 'boo']
    }
  }
, computed: {
    ...sync('foo.*')
   // items: sync('foo.*') // that would be great, but does not work
  }
</script>

What I'm trying to achive is, to generate dynamically a form, where I only pass the path in vuex ('foo.*') and the values I want to be mutable (['bar', 'boo']) and then iterate over them to generate my form. Do you have any idea how I could achive this? When I use ...sync('foo.*'), the attributes bar and boo are available in the component, but how can I dynamically call them? Do you know what I mean? :) Your help is desired. :smile:

davestewart commented 6 years ago

I obviously misread the docs. Thought thats what I need. 😕

Yes, Pathify copies Vuex Helpers with the Array / Object mapping styles.

As you've understood now, this is array syntax vs array access!

I want to be more generic while creating forms. What I'm trying to achive is, to generate dynamically a form, where I only pass the path in vuex (here 'foo.') and the values I want to be mutable (['bar', 'boo']) and then iterate over them to generate my form.

Well, the Pathify component helpers (get, sync) are essentially sugar functions to generate actual computed properties, which are themselves functions:

https://davestewart.github.io/vuex-pathify/#/api/component?id=sync

// pathify
computed: {
  items: sync('products/status')
}
// vanilla js / vue
computed: {
  status: {
    get () { 
      return this.$store.state.products.status
    },
    set (value) {
      return this.$store.commit('products/SET_STATUS', value)
    },
  }
}

So the questions is - could you do this with normal computed properties?

Or do you need something like:

<div>
    <input v-for="(item, index) in items" :value="item.value" @change="setItem(index, $event.target.value)"/>
</div>
computed: {
    items () {
        return this.$store.state.items
    }
},
methods: {
    setValue (index, value) {
        this.$store.commit('setValue', {index, value})
    },
}

However, I think the issue with this, is that you need some way to pass an index, and sync would have no way to know what that index is.

Generally with items in a collection like that, I'd edit each row of data in a new component and would probably commit the entire result using a form submit.

If you can have a play and can illustrate to me that I've missed something, I'll be happy to take a look at it!

haexhub commented 6 years ago

Thank you @davestewart :smile: You were right. I had to create my own update function and it looks like this: store/index.js


export const state = () => {
  templates: {
    offer: {
      foo: { value: 'Hello' }
    , bar: { value: 'World' }
} } }

export const mutations = {
  ...make.mutations(this.state)
, update_foo (state, { index, value }) {
    if (state.hasOwnProperty(templates.offer[index].value))
      state.templates.offer[index].value = value
  }
}

pages/index.vue

<template>
  <input v-for = '(item, index) in items' :value = 'item.value' @input = 'update(index, $event)'/>
</template>

<script>
export default {
  props: {
    path: {
      type: String
    }
  }
, computed: {
    items: sync('templates@offer') // works, but I want to use the prop path!
  // items () { return sync(this.path) } // results in: items: { get () {}, set () {} }
  }
, methods: {
    update (index, value) {
      this.$store.commit('update_foo', { index, value } )
    }
  }
}
</script>

The Vue docs says, that you need to use a function inside computed properties, if you want to access _ this. But with this approach you get items: { get(), set() }, but I want items: { foo: { value: 'Hello' }, bar: { value: 'World'}}.

davestewart commented 6 years ago

OK.

So not quite sure your response was a comment or a question?

Are you still asking if you can get pathify or / and Vue computed properties to work with arrays?

As I understand it, as computed cannot have any context to the array to each item, you have to use methods.

The only other way to do it would be to build separate components for each item, then use computed properties inside each item to refer to the original array item:

<div v-for="(item, index) in items">
    <item-editor :item="item" :index="index" />
</div>

Inside item, you would have some kind of path to Vuex.

My personal opinion though is that when you're updating items like this, you would be better using an isolated form that updates the item on submit:

function onSubmit(index, item) {
  this.$store.commit('updateItem', {index, item})
}

I can't think of many use cases where you would want immediate updates in the store for a single item property in a list of items.

As I don't think this is something Pathify can (or should!) help with, I'll close this ticket unless you have any further thoughts.