I'm running into a strange issue with a complex loop I'm attempting within my component.
<template v-for="(scorecard, scorecardIndex) in scorecards">
<template v-for="(property, propertyIndex) in properties">
<!-- Primary player -->
<tr v-if="scorecardIndex === 0"
v-bind:key="propertyIndex"> // THIS LINE
</tr>
</template>
<!-- Secondary players, should only show score -->
<tr v-if="scorecardIndex > 0"
v-bind:key="scorecardIndex">
</tr>
</template>
Where I'm setting v-bind:key="propertyIndex" I'm getting the following error within VSCode
[vue/valid-v-for]
Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive.
The weird thing about this is that I don't actually get an error on my vue compiler, just within VSCode. This makes me think it has something to do directly with VSCode and not necessarily Vue itself.
You're getting this error from eslint which checks your code for possible errors and bad coding style.
When you use v-for on <template>, you need to make sure you define key on each top-level element within the <template>. This is because <template> is not an actual DOM element (within a Vue template) and so each element within it will be repeated at the same level in the DOM which is why key is required on each element at that DOM tree level.
You have a nested <template>, each using v-for, so it gets a bit more complicated. You need to make sure that the key of the first <tr> uses variables defined by both v-fors. Although in your specific case, you are using v-if to limit the element being created to only the first row, so eslint is being too pedantic here. You can silence the warning like this:
<!-- eslint-disable-next-line vue/valid-v-for -->
<tr v-if="scorecardIndex === 0"
v-bind:key="propertyIndex">
</tr>
But I think you have the order around the wrong way. It makes more sense to first check if it is the first row and then repeat a bunch of <tr>s, instead of the other way around:
<template v-if="scorecardIndex === 0">
<!-- Primary player -->
<tr v-for="(property, propertyIndex) in properties"
v-bind:key="propertyIndex">
</tr>
</template>
Lastly, you will get some duplicate key warnings because both sets of <tr>s will use keys 0, 1, 2, and so on. Maybe prefix them:
<template v-for="(scorecard, scorecardIndex) in scorecards">
<template v-if="scorecardIndex === 0">
<!-- Primary player -->
<tr v-for="(property, propertyIndex) in properties"
v-bind:key="'primary-' + propertyIndex">
</tr>
</template>
<!-- Secondary players, should only show score -->
<tr v-if="scorecardIndex > 0"
v-bind:key="'secondary-' + scorecardIndex">
</tr>
</template>
Related
I use the v-money package to mask a money input field like this:
<template v-if="serviceContract.services">
<tr
v-for="service in serviceContract.services"
:key="service.id"
>
<td>
<money
v-model.trim="service.unit_price_excl_tax"
v-bind="money"
#input="calcPricesFromUnitPrice(service)"
/>
</td>
</tr>
</template>
Methods:
calcPricesFromUnitPrice(service) {
// here I do some calculation stuff with that service variable
}
I always get the Vue warning
You may have an infinite update loop in a component render function.
I have no idea what this means or why it is giving me this warning.
I would like to iterate rows in a table based over a range. But all examples I've seen show how to do this for a constant for example,
<div>
<span v-for="n in 10">{{ n }} </span>
</di
How can I do this for a variable similar to this,
<table>
<tr v-for="(el, index) in form.repeat" :key="index">
<td>hello</td>
</tr>
</table>
This variable form.repeat is set in a form on the page.
Thanks
I had this issue week, my workaround for it was setting a variable in your data section to the number of rows you want. In my case based on the number of devices my backend returned the length of the list as part of the response. When Vue receives this response. I assign the variable that I created earlier in my data section to this number and I plug that into my v-for loop.
In the data section:
data: () => ({'num_pis': 0})
In the template part, reference the number. This is just a snippet, I'm using a v-data-table from Vuetify.
<template slot="items" slot-scope="props">
<td class="table-content-style">{{ props.item.metric_name }}</td>
<td v-for="i in num_pis">
{{ props.item[i] }}
</td>
</template>
Codepen Example
When using dynamic component in vue, we could use component or html tag such as div as the tag name:
<component :is="comp-name"></component>
or:
// assume that the root tag of comp-name is div
<div :is="comp-name"></div>
So what's the difference between the 2 ways? Are the same?
The "is" attribute is not Vue specific, it comes from the Custom Element spec.
See also What is HTML "is" attribute?
But obviously Vue has to implement it on its own for its compilation, mimicking the Custom Element spec.
In the example you show, I guess it will not matter, as in both cases the tag (<component> or <div>) will be replaced by the Vue component instance. This is the typical situation of using is attribute to switch between several possible components ("dynamic components").
However it starts to matter when you try to use Custom Elements / Vue components (with runtime compilation) in some HTML elements that restrict the type of child elements they can have, as explained in the DOM Template Parsing Caveats section of Vue guide:
Some HTML elements, such as <ul>, <ol>, <table> and <select> have restrictions on what elements can appear inside them, and some elements such as <li>, <tr>, and <option> can only appear inside certain other elements.
In these cases, the is attribute may not be (only) used to switch between dynamic components, but to comply with HTML restrictions (in order to avoid the browser behaving unexpectedly to our custom components) while still replacing them later on by our custom components.
Here is a quick demonstration with a <table>:
Vue.component('my-row', {
template: '#my-row',
});
new Vue({
el: '#app',
});
td,
th {
border: 1px solid black;
}
<script src="https://unpkg.com/vue"></script>
<div id="app">
<table id="table1">
<tr>
<th>Table</th>
<td>1</td>
</tr>
<!-- Below Custom component will be stripped out of the table. Exact result may differ depending on browsers -->
<my-row></my-row>
</table>
<hr />
<table id="table2">
<tr>
<th>Table</th>
<td>2</td>
</tr>
<!-- Valid TR row will be correctly replaced by Custom component -->
<tr is="my-row"></tr>
</table>
</div>
<template id="my-row">
<tr>
<th>Header</th>
<td>Cell</td>
</tr>
</template>
Result:
in Firefox, the <my-row> tag is rendered outside and above the <table>.
same in Chrome.
I want to make a grid component which accepts as slot additional column cell.
<grid>
<td slot="additional-column">...</td>
</grid>
In the actual component:
<template>
<table>
<div v-for="item in items">
...
<slot name="additional-column"></slot>
</div>
</table>
</template>
Unfortunately the slot starts repeating itself and this is something vuejs doesn't like.
Duplicate presence of slot "additional-column" found in the same render tree - this will likely cause render errors.
Does anybody know how can I deal with this issue?
Thanks in advance!
This definitely seems to be your issue. You could do it this way too (as described here). See last paragraph before the subheading Destructuring.
Parent:
<grid>
<td :slot="['additional-column', item].join('-')" v-for="item in items">
...
</td>
</grid>
Child:
<table>
<div v-for="item in items">
...
<slot :name="['additional-column', item].join('-')"></slot>
</div>
</table>
PS: I have NOT tried this out. If you have difficulties I could create a fiddle and share.
Make the item a nested component (which you'll be repeating using v-for) and render the additional-column slot in that particular component.
That's the proper way to overcome this issue. The idea is that you need to have one single slot with a particular name per component.
That is, you could do it that way, which is a very rough version of it but nicely outlines the idea:
<!-- Grid Template -->
<template>
<table>
<GridItem :item="item" v-for="item in items">
<!-- Maybe even pass that column conditionally, based on the item data -->
<div slot="additional-column">
Content of column
</div>
</GridItem>
</table>
</template>
<!-- GridItem Template -->
<template>
<div>
<div>Column 1</div>
<div>Column 2</div>
<slot name="additional-column" />
</div>
</template>
I tried to continue with the example on the vuejs website. I tried to add images and a transition state when I sort data.
However it doesn't work. I have tried to add the following line to make it works but it doesn't:
<tbody name="table-row" is="transition-group">
Do you have some ideas for me?
https://codepen.io/wooza/pen/wezqXP
https://codepen.io/anon/pen/gRmxwJ
https://v2.vuejs.org/v2/guide/transitions.html#List-Transitions
Unlike <transition>, it renders an actual element: a <span> by
default. You can change the element that’s rendered with the tag
attribute.
<transition-group tag="tbody" name="table-row">
<tr v-for="entry in filteredData" :key="entry.name">
//...
</tr>
</transition-group>