Using several v-for loops within a single Vue component file

I am attempting to populate a table with data fetched from my API using three for loops.

  const events = await app.$axios.get('/api/events/')
  const markets = await app.$axios.get('/api/markets/')
  const runners = await app.$axios.get('/api/runners/')

The challenge I face is ensuring that the indexing of table rows is based on "runners" because there will be more runners than markets, and markets will have more rows than events.

Here's how I envision it:

Event Market
           Runner
           Runner
           Runner
      Market
           Runner
           Runner   

However, when I attempt to use multiple for loops in the same Vue file, I encounter the following error.

duplicate attribute key

I am puzzled as to why this error occurs when using "id" in separate loops. My query is: How can I populate the table based on the runner index for each row?

Below is the code snippet I have worked on so far.

<template>
    <div class="course-list-row">
      <th style="width:5%"> Start date </th>
      <th style="width:5%"> Event name</th>
      <th scope="col">Market</th>
      <th scope="col">Runner</th>

      <tr v-for="(event, index) in events" :key=id
          v-for="(market, index) in markets" :key=id
          v-for="(runner, index) in runners" :key=id>
        <td>{{ event.start_time }} </td>
        <td>{{ event.event_name }} </td>
        <td>{{ market.name }} </td>
        <td>{{ runner.name }} </td>

        <td />
      </tr>
    </div>
  </template>

  <script>
    export default {

      async asyncData({
        app
      }) {

        try {
          const events = await app.$axios.get('/api/events/')
          const markets = await app.$axios.get('/api/markets/')
          const runners = await app.$axios.get('/api/runners/')

          return {
            events: events.data.results,
            markets: markets.data.results,
            runners: runners.data.results,
            error: false
          }
        } catch (e) {
          console.log('error', e)
          return {
            events: [],
            markets: [],
            runners: [],
            error: true
          }
        }
      },
    };

  </script>


  <style>
    th,
    td {
      font-family: ‘Lato’, sans-serif;
      font-size: 12px;
      font-weight: 400;
      padding: 10px;
      text-align: left;
      width: 0%;
      border-bottom: 1px solid black;

    }

  </style>

The responses from the API include unique IDs for reference, as shown below.

/api/events

{
  "count": 80,
  "next": null,
  "previous": null,
  "results": [{
    "id": 103,
    "sport_id": "9",
    "event_name": "Guilherme Clezar vs Uladzimir Ignatik",
    "start_date": "12-11-19",
    "status": "open",
    "event_id": 1273804660340017
  }]
}

/api/markets

{
  "count": 128,
  "next": "http://localhost:8000/api/markets/?page=2",
  "previous": null,
  "results": [{
    "id": 151,
    "market_id": 1273804670840017,
    "market_name": "Moneyline",
    "status": "open",
    "volume": 1107.5453,
    "event": 103
  }]
}

Answer №1

When re-rendering, keys are essential for calculating differences in DOM structure. It is crucial that keys are unique within the scope of a parent DOM node.

To ensure uniqueness, try adding prefixes to your keys.

v-for="(event, index) in events" :key="`event_${index}`" 

EDIT: One potential reason for errors could be that the id variable is not defined. Consider using index instead.

Additionally, it's worth questioning whether utilizing three loops on the same element is a viable approach. If you haven't attempted this before, perhaps consider concatenating data in a computed property for more predictable results.

Answer №2

It has been noted in the comments that having multiple v-for statements on the same element is not possible. Instead, you can structure your code like this:

<div class="course-list-row">
        <th style="width:5%"> Start date </th>   
        <th style="width:5%"> Event name</th>
        <th scope="col">Market</th>
        <th scope="col">Runner</th>

        <tr v-for="(event, index) in events" :key=id>
            <td>{{ event.start_time }} </td>
            <td>{{ event.event_name }} </td>
            <td></td>
            <td></td>
        </tr>

        <tr v-for="(market, index) in markets" :key=id>
            <td></td>
            <td></td>
            <td>{{ market.name }} </td>
            <td></td>
        </tr>

        <tr v-for="(runner, index) in runners" :key=id>  
            <td></td>
            <td></td>
            <td></td>
            <td>{{ runner.name }} </td>
        </tr>
 </div>

OR

To simplify the templating, you can merge the arrays into a single array with id and type, and render it like so:

    <tr v-for="(entry, index) in combined" :key=id>
        <td>{{entry.type == 'event' ? event.start_time : ''}} </td>
        <td>{{ entry.type == 'event' ?  event.event_name : ''}} </td>
        <td>{{ entry.type = 'market' ? market.name : ''}}</td>
        <td>{{ entry.type == 'runner' ? runner.name : ''}}</td>
    </tr>

Answer №3

After reviewing the comments, I am summarizing some issues with the original code.

An HTML element cannot have the same attribute repeated multiple times. While Vue templates are not exactly pure HTML, they follow similar parsing rules where duplicates are ignored. The initial code includes both v-for and key attributes duplicated on the <tr>.

Another concern is the value assigned to the key. Using :key=id attempts to set the key equal to the value of a property named id. However, it's unclear where this property is defined. By default, Vue will search for it within the Vue instance itself. It seems more appropriate to use the id from the event, market, or runner objects. To achieve this, you should write :key="event.id", etc.

The purpose of the code is not entirely clear, but based on my interpretation, below is an attempt to provide the desired output:

new Vue({
  template: `
    <table border>
      <thead>
        <th>Start date</th>   
        <th>Event name</th>
        <th>Market</th>
        <th>Runner</th>
      </thead>
      <tbody>
        <template v-for="event in events">
          <tr :key="'event' + event.id">
              <td>{{ event.start_date }}</td>
              <td>{{ event.event_name }}</td>          
          </tr>
          <template v-for="market in marketsFor[event.id]">
            <tr :key="'market' + market.id">
              <td></td>
              <td></td>
              <td>{{ market.market_name }} </td>
            </tr>
            <tr v-for="runner in runnersFor[market.id]" :key="'runner' + runner.id">
              <td></td>
              <td></td>
              <td></td>
              <td>{{ runner.runner_name }}</td>
            </tr>
          </template>
        </template>
      </tbody>
    </table>
  `,
  
  el: '#app',
  
  data () {
    return {
      events: [
        {
          "id": 103,
          "event_name": "Guilherme Clezar vs Uladzimir Ignatik",
          "start_date": "12-11-19"
        }, {
          "id": 104,
          "event_name": "Event 2",
          "start_date": "13-11-19"
        }        
      ],
      markets: [
        {
          "id": 151,
          "market_name": "Moneyline",
          "event": 103
        }, {
          "id": 152,
          "market_name": "Market 2",
          "event": 103
        }, {
          "id": 153,
          "market_name": "Market 3",
          "event": 104
        }
        
      ],
      runners: [
        {
          "id": 1,
          "runner_name": "Runner 1",
          "market": 151
        }, {
          "id": 2,
          "runner_name": "Runner 2",
          "market": 151
        }, {
          "id": 3,
          "runner_name": "Runner 3",
          "market": 152
        }, {
          "id": 4,
          "runner_name": "Runner 4",
          "market": 153
        }
      ]
    }
  },
  
  computed: {
    marketsFor () {
      const markets = {}
      
      for (const market of this.markets) {
        const event = market.event
        markets[event] = markets[event] || []
        markets[event].push(market)
      }
      
      return markets
    },
    
    runnersFor () {
      const runners = {}
      
      for (const runner of this.runners) {
        const market = runner.market
        runners[market] = runners[market] || []
        runners[market].push(runner)
      }
      
      return runners
    }
  }
})
<script src="https://unpkg.com/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1b6d6e7e5b29352d352a2b">[email protected]</a>/dist/vue.js"></script>
<div id="app"></div>

I made adjustments to some properties such as changing start_time to

start_date</code and <code>market.name
to market.market_name. These changes were necessary to align the properties in the template with the example data provided.

The main solution involves using computed properties to create object maps that help locate the respective children at each level. While there are alternative approaches, navigating the hierarchy within the template appears crucial. Simplifying the template may shift complexity to the JavaScript section of the component.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue with Gatsby source plugin displaying only the final item in the array on GraphQL

After running build, I noticed that 6 objects are displayed in my drinks array when I console log. The same happens when I run develop. However, when I query graphQL, only the last object in my array is accessible. Being new to Gatsby and graphQL, I includ ...

The steps to close my PWA after sharing data using Web Share Target

After sharing a picture from Google Photos to my PWA (installed through Chrome on Android and utilizing the code mentioned here: https://web.dev/web-share-target/), I am directed to my PWA page. How can I automatically return to Google Photos? If it ma ...

Converting a JS result set into JSON format

Having an issue with converting code from XML output to a JSON object instead. Here is the current code: public String evaluate() throws Exception { // Code block here } I need assistance in using GSON JSON utility methods to convert the result s ...

Fixing Firebase and React errors in JavaScript functions

Thank you for your understanding. I am currently integrating Firebase into my website. However, when I invoke the signup function in FormUp.js (which is declared in AuthContext.js), it does not reference the function definition. As a result, the function c ...

Expanding a container component with React TypeScript

I'm in the process of developing a foundational class, encapsulating it within a container, and extending it in my various components. Here's a basic example: let state = new State(); class Base extends React.Component<{}, {}> { } const ...

React Native formInput Component with Underline Decoration

Utilizing the FormInput element in React Native Elements, I have observed a line underneath each FormInput component. Interestingly, one line appears fainter than the other. https://i.sstatic.net/ZD8CI.png This is how the form looks: <View style={sty ...

Using a pool.query with async/await in a for-of loop for PostgreSQL

While browsing another online forum thread, I came across a discussion on using async/await with loops. I attempted to implement something similar in my code but am facing difficulties in identifying the error. The array stored in the groups variable is f ...

Incorporating sass into your Antd and Create React App workflow

Trying to combine SASS with antd and CRA has been a bit of a challenge for me. Despite following various tutorials, most of them seem outdated and result in errors. Fortunately, I stumbled upon an article that actually works smoothly link However, I can& ...

Click on the grid in JavaScript to rearrange it with a simple tap

I've hit a roadblock while working on my project, specifically with reorganizing my grid using JQuery. To better illustrate the issue, I've simplified it into a fiddle: http://jsfiddle.net/tylerbuchea/QgAqV/ $('div').bind('click&a ...

Does the layout.tsx file in Next JS only affect the home page, or does it impact all other pages as well?

UPDATE After some troubleshooting, I've come to realize that the issue with my solution in Next JS 13 lies in the structure of the app. Instead of using _app.tsx or _document.tsx, the recommended approach is to utilize the default layout.tsx. Althou ...

Is there a way to display only the first 5 divs and then include a "Load more" button to view the rest

I've got the code below and everything is working fine. However, when I add that script code into my project, it doesn't work. There are no errors in the console. Why is this happening? In Fiddle, everything runs smoothly. Could it be due to usin ...

Click to toggle with special effect

Similar Question: How to detect a click outside an element? <ul id="rightNav"> <li id="SettingOpt"> <a href="javascript:void(0)">username<span class="fabShopSprite iRecentIcon iUserIcon"></span></a ...

Is there a way to retrieve the day of the week based on the date provided by the user

function retrieveWeekday() { var input = document.getElementById("input"); } <form> <input type="date" placeholder="dd:mm:yy" id="input"/> <input type="button" value="get weekday" onclick="retrieveWeekday()"/> ...

AngularJS 1.7.x's ngRoute tabs navigation post authentication

In my current project, I am using ngRoute to route users to the login page and upon successful login, they should land on the dashboard page. However, I am facing a problem with the dashboard page where I need to have two or more tabs that render HTML pag ...

Experiencing browser crashes following the incorporation of asynchronous functions into a JavaScript file. Seeking solutions to resolve this

In my recent project, I developed a basic online store application using vanilla javascript and ES6 classes. The shop items are stored in a JSON file which I used to populate the user interface. To implement functions like "addToCart", "quantityChange", a ...

Navigate through each element with a specific class name in a block of HTML code using

Currently, I am dealing with an HTML string that is being retrieved through AJAX. Let's assume it looks like this: var htmlString = '<div class="post"></div><div class="post"></div>'; I want to find a way to iterat ...

Using Vue.js class components to automatically update the view when the data in an RxJS Observable changes

Note: Just starting out with Vue.js here, and coming from an Angular background, I decided to go the Class Component route. I understand it may not be the recommended way in Vue.js, but this project is more of a fun experiment for me to try new things. Th ...

What is the process for importing fresh components from node_modules into the project?

Help! I've been using Webpack and WordPress for a while, but recently decided to dive into Vue.js. The problem is, I'm completely lost. I managed to integrate Vue with WordPress and Webpack after 5 days of work, but I still don't understand ...

The inclusion of HttpClient is causing issues with the functionality of my component

Currently, I am facing an issue with my Angular service called ConnexionService. The problem arises when I try to incorporate CSV files into this service using HttpClient. Strangely, the component associated with this service fails to display once HttpClie ...

What is the best way to expand a div in a downward direction exclusively?

My task involves a parent div containing a centered child div. My goal is to have the child div grow downwards only upon clicking it, not in both directions. Initial state of the two divs: https://i.sstatic.net/cWYyV.png Outcome after clicking on div 2: ...