1

I'm curious if there's a better way of getting properties from a reduced object within a Vue or wrapper DOM element.

For example, I have these two data objects in my component:

player: [{
   active: true,
   id: 0,
   name: "Alfred",
   action: 0
}, {
   active: true,
   id: 1,
   name: "Bruce",
   action: 1
}],
actions: [{
   id: 0,
   label: "Give advice",
   description: "Player advises others"
}, {
   id: 1,
   label: "Fight",
   description: "Player fights enemy"
}]

...and I'd like to loop through a list of options and return all results, with properties of the action within each player:

------------------------------------------------------------------
Active?   ID    Name     Label         Description
------------------------------------------------------------------
true      0     Alfred   Give Advice   Player advises others
true      1     Bruce    Fight         Player fights enemy

I know I can loop through the players like so:

<table>
  <tr v-for="(player, index) in players" :key="index">
    <td>{{ player.active }}</td>
    <td>{{ player.id }}</td>
    <td>{{ this.getActionProp(player.action, 'label') }}</td>
    <td>{{ this.getActionProp(player.action, 'description') }}</td>
  </tr>
</table>

With a method like this:

getActionProp(id, prop){
  return this.actions.reduce((acc, cur) => {
    return cur.id == id ? cur[prop] : acc
  }, {})
}

But it seems like that has to reduce twice for each row in the table. Is there a better way to get these props or at least to make it so they only reduce once per row?

Something like:

<table>
  <tr v-for="(player, index) in players" :key="index">
    <td>{{ player.active }}</td>
    <td>{{ player.id }}</td>
    <template :action="getAction(player.action)">
      <td>{{ action.label }}</td>
      <td>{{ action.description }}</td>
    </template>
  </tr>
</table>

with getAction() being a method that reduces and returns the matching action object.

Thank you for your time and I appreciate your guidance.

rozsazoltan
  • 2,831
  • 2
  • 7
  • 20
Ben Coffin
  • 483
  • 4
  • 13
  • **If possible, avoid using array `index` for the `:key` value**, as it may happen that you insert a new element, for example, into the middle of a 10-element array at position 5, and from then on, the saved values will shift for elements with an index greater than 5. Instead, try using a fixed "key" that uniquely identifies that record in the array, regardless of whether it is in the 1st, 5th, 10th, etc. position. This will save you from some inconveniences, such as when sorting the array elements. – rozsazoltan Jun 29 '23 at 21:02
  • [Maintaining State with `key`](https://vuejs.org/guide/essentials/list.html#maintaining-state-with-key) -- [**Why not always use the `index` as the `key`** in a vue.js for loop?](https://stackoverflow.com/questions/44531510/why-not-always-use-the-index-as-the-key-in-a-vue-js-for-loop) – rozsazoltan Jun 29 '23 at 21:02
  • 1
    Thank you. I didn't know that. Maybe the player.id would've been better. – Ben Coffin Jun 29 '23 at 21:33

1 Answers1

2

You can just just map the actions in a computed and prepare the table rows:

Vue SFC Playground

<script setup>
import { computed, reactive } from 'vue'

const players = reactive([{
  active: true,
  id: 0,
  name: "Alfred",
  action: 0
}, {
  active: true,
  id: 1,
  name: "Bruce",
  action: 1
}])

const actions = reactive([{
  id: 0,
  label: "Give advice",
  description: "Player advises others"
}, {
  id: 1,
  label: "Fight",
  description: "Player fights enemy"
}])

const playerTable = computed(() => players.map(player => {
  return {
    ...player,
    action: actions.find(action => action.id === player.action)
  }
}))
</script>

<template>
  <h1>TEST</h1>
  <table>
    <tr v-for="player in playerTable" :key="player.id">
      <td>{{ player.active }}</td>
      <td>{{ player.id }}</td>
      <td>{{ player.action.label }}</td>
      <td>{{ player.action.description }}</td>
    </tr>
  </table>
</template>
rozsazoltan
  • 2,831
  • 2
  • 7
  • 20
Alexander Nenashev
  • 8,775
  • 2
  • 6
  • 17