1

Trying to render an array from vuex through "v-for".

The "player-card" component is not rendered. But the "td" solution works correctly.

My example on JSFiddle

HTML:

    <div id="app">
      <button v-on:click="moveItem">
        Move Item
      </button>

      <table cellspacing="2" border="1" cellpadding="5">
        <tr>
          <td v-for="(item, item_idx) in getItems" v-bind:key="item.col">{{ (item.card)? item.card.name : 'none' }}</td>
        </tr>
        <tr>
          <player-card v-for="(item, item_idx) in getItems" v-bind:key="item.col" v-bind:item="item"></player-card>
        </tr>
      </table>
      <br/>
      <p>{{msg}}</p>
    </div>

Store:

    const store = new Vuex.Store({
      state: {
        items: [{ col: 0, row: 0 },
                { col: 1, row: 0 },
                { col: 2, row: 0, card: { name: "hello" } } ]
      },
      getters: {
        getterItems: state => { return state.items; }
      },

      mutations: {
        MOVE_ITEM: state => {
          state.items[0].card = state.items[2].card;
          delete state.items[2].card;
          state.message = JSON.stringify(state.items);
        }
      }

    });

Component:

    Vue.component('player-card', {
      props: {
        item: {
          type: Object,
          required: true
        }
      },
      template: '<td>{{ (item.card)? item.card.name : "none" }}</td>'
    });

App:

    new Vue({
      el: '#app',
      store,
      data: function() {
        return {
          msg: ''
        }
      },
      computed: {
        getItems() { return this.$store.getters.getterItems; }
      },
      mounted: function() { 
        this.msg = JSON.stringify(this.getItems); 
      },
      methods: {
        moveItem() {
          this.$store.commit('MOVE_ITEM');
          this.msg = JSON.stringify(this.getItems);
        }
      }
    });

I have already tried many solutions, but have not found a simple one. Maybe someone will offer a different architectural solution.

3 Answers 3

2

You just need to change this line:

<player-card v-for="(item, item_idx) in getItems" v-bind:key="item.col" v-bind:item="item"></player-card>

to this:

<td is="player-card" v-for="(item, item_idx) in getItems" v-bind:key="item.col" v-bind:item="item"></td>

This is required because your template is specified in the DOM. The browser will parse the template markup before Vue gets anywhere near it. HTML parsing rules only allow certain elements to be the direct children of a <tr>. Any other element will be pulled out of the <table>. By the time Vue comes to parse the template the <player-card> element will already have been moved outside the <table>.

This would not be a problem if you used one of the other techniques for specifying the template.

The workaround is to use the is attribute to specify the component instead of the tag name.

This is explained in the docs here:

https://v2.vuejs.org/v2/guide/components.html#DOM-Template-Parsing-Caveats

Sign up to request clarification or add additional context in comments.

Comments

1

You need to use the special is property to get around the imposed element structure where <tr> expects <td> as children but gets <player-card> (pre-transformed). Additionally, the way you are mutating your Array is causing reactivity problems. Consider my change.

const store = new Vuex.Store({
  state: {
    items: [{
        col: 0,
        row: 0
      },
      {
        col: 1,
        row: 0
      },
      {
        col: 2,
        row: 0,
        card: {
          name: "hello"
        }
      }
    ]
  },
  getters: {
    getterItems: state => {
      return state.items;
    }
  },

  mutations: {
    MOVE_ITEM: state => {
      // Move the last element to the front
      state.items = [
        ...state.items.slice(-1),
        ...state.items.slice(0, -1)
      ];
      state.message = JSON.stringify(state.items);
    }
  }

});

Vue.component('player-card', {
  props: {
    item: {
      type: Object,
      required: true
    }
  },
  template: '<td>{{ (item.card)? item.card.name : "none" }}</td>'
});

new Vue({
  el: '#app',
  store,
  data: function() {
    return {
      msg: ''
    }
  },
  computed: {
    getItems() {
      return this.$store.getters.getterItems;
    }
  },
  mounted: function() {
    this.msg = JSON.stringify(this.getItems);
  },
  methods: {
    moveItem() {
      this.$store.commit('MOVE_ITEM');
      this.msg = JSON.stringify(this.getItems);
    }
  }
});
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>

<div id="app">
  <button v-on:click="moveItem">
        Move Item
      </button>

  <table cellspacing="2" border="1" cellpadding="5">
    <tr>
      <td v-for="(item, item_idx) in getItems" v-bind:key="item.col">{{ (item.card)? item.card.name : 'none' }}</td>
    </tr>
    <tr>
      <td is="player-card" v-for="(item, item_idx) in getItems" v-bind:key="item.col" v-bind:item="item"></td>
    </tr>
  </table>
  <br/>
  <p>{{msg}}</p>
</div>

Comments

0

Also in my question was the problem of updating the arrays in vuex.

Information about this "Common Beginner Gotchas"

mutations: {
MOVE_ITEM: state => {
  // Move card
  state.items[0].card = state.items[2].card;
  delete state.items[2].card;

  // Simple deep array cloning
  state.items = JSON.parse(JSON.stringify(state.items));

  state.message = JSON.stringify(state.items);
}}

Updated JSFiddle

Thank you all very much.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.