VueJS slots with v-for loop do not display proper elements - vue.js

I'm trying to create a component that displays a subset of items passed to it.
So far I have a 'sublist' component with named slots as follows:
...
data: () => ({
startItem : 0
})
...
<template>
<div>
<slot v-for="ctr in maxItems" :name="'s-' + (ctr + startItem - 1)"></slot>
</div>
</template>
In the parent I do the following:
<sublist :max-items="5">
<div v-for="(i,index) in items" :slot="'s-'+index">
{{index}}
</div>
</sublist>
When it loads, everything renders fine:
0 1 2 3 4
However when I increment startItem in the sublist component, the output becomes:
5 1 2 3 4
So it removes the 0th slot and stuffs slot 5 in its place. What is a proper way to replace the slots or make them "dynamic"? I'm using VueJS 2.4.2

Thanks to #SteveHolgado I found the answer: had to add :key attribute in the parent v-for and wrap each slot in the child in its own div:
<template>
<div>
<div v-for="ctr in maxItems">
<slot :name="'s-' + (ctr + startItem - 1)"></slot>
</div>
</div>
</template>

Related

v-for nested inside another v-for AND an array issue

OK so this is what I am working with. I have 2 arrays The first array contains an ID number. That ID matches up to the second array minus the presence-chat. string.
Array 1:
Rooms: Array
0: Object
id: 1
1: Object
id: 2
2: Object
id: 3
Array 2:
roomUserCount: Object
channels: Object
presence-chat.1: Object
user_count: 2
presence-chat.2: Object
user_count: 3
presence-chat.3: Object
user_count: 4
What I am trying to figure out is how would I write my v-for command to run through the whole thing. Whats happening before is another v-for that is saying
v-for="room in rooms" :key="room.id"
Now for the next step nested inside the first v-for I need to run another one to fill in the appropriate data.
the nested code would look like this but I need to fill it in.
<div v-for="" :key="">
<div v-if="">
{{ user_count }}
</div>
<div v-else="">
0
</div>
</div>
What do I do?
That's actually one array (rooms) that indexes an object (roomUserCount).
Lookup the object by room.id in roomUserCount.channels. If the object exists, render its user_count:
<template>
<div v-for="room in rooms" :key="room.id">
<div v-if="roomUserCount.channels['presence-chat.' + room.id]">
{{ roomUserCount.channels['presence-chat.' + room.id].user_count }}
</div>
<div v-else>
0
</div>
</div>
</template>
demo 1
Alternatively, you could simplify the template with optional chaining (?.) and nullish coalescing (??):
<template>
<div v-for="room in rooms" :key="room.id">
{{ roomUserCount.channels['presence-chat.' + room.id]?.user_count ?? 0 }}
</div>
</template>
demo 2

Vue - passing v-for index from parent to child component

I've done the research but can't find a good answer. My parent and child component code is below. How do I pass the index for the v-for loop in the parent to the child component for use there? I want to hide any of the gauges past #4 for mobile screens, but show all of them on a desktop.
Parent:
<div class="col-md-8 col-xs-6">
<div class="row flex-nowrap">
<data-block
v-for="(gauge, index) in device.gauges"
:metric="gauge.metric"
:value="gauge.value"
:unit="gauge.unit"
:alarm="gauge.alarm"
:idx="index">
</data-block>
</div>
</div>
Child:
app.component('data-block', {
props: ['alarm', 'metric','value','unit','idx'],
template: `<div v-bind:class="'col card px-0 text-center border' + ((alarm) ? ' border-danger':' border-success') + ((idx > 3) ? ' d-none d-md-block':'')">\
<div class="card-header p-1">{{metric}}</div>\
<div class="card-body p-1 align-middle">\
<h1 class=" text-center display-1 font-weight-normal text-nowrap">{{value}}</h1>\
</div>\
<div class="card-footer p-1">{{unit}}</div>\
</div>`,
created: ()=> console.log(this.idx) //yields "undefined"
})
You're passing the idx prop correctly, but Instead of checking its value inside created hook, try displaying it in the template instead, to make sure it's not an issue with timing (it might not be defined when the child component is created):
<div>{{idx}}</div>
Also, to make the code easier to read and write, I would suggest you to move the static classes to class attribute and the dynamic classes to v-bind:class attribute, and also make it multiline:
template: `
<div
class="col card px-0 text-center border"
:class="{
'd-none d-md-block': idx > 3,
'border-danger': alarm,
'border-success': !alarm
}"
>
...
`

Passing an Array as Prop, using v-for in the child does not work

In vue, i want my parent to pass an array to a child as a prop. The child then should render all contents of that array with v-for.
I used :steps="[1, 2, 3]" to pass in the array as prop and used a div as container with :for="step in steps", but it only renders one div with no content.
*** parent.vue template:
<recipesListItem
title="Name"
description="text"
:steps="[1, 2, 3]">
</recipesListItem>
*** child.vue template:
<div>
{{ title }}<br>
{{ description}}
<div :for="step in steps">
{{ step }}
</div>
</div>
*** child.vue script:
import Vue from 'vue'
export default Vue.extend({
name: 'recipesListItem',
props: {
[ ... ]
steps: Array
}
})
Expected result: title, description and 3 divs with the contents "1", "2" and "3" are shown
Actual result: title description and only 1 div with no content is shown
Thanks in advance for any help
Change your child.vue template like this.
<div>
{{ title }}<br>
{{ description}}
<div v-for="step in steps" :key="step">
{{ step }}
</div>
</div>
You should "v-for" to render a list or array and don't forget to bind key which is important for rendering. May be :key binding is not required for simple array like in your case. And if you do bind :key with your "step" then remember that your "step" values should be unique else you can use the following approach.
<div>
{{ title }}<br>
{{ description}}
<div v-for="(step,index) in steps" :key="index">
{{ step }}
</div>
</div>
Also, you can use vue chrome debugger to make sure your list passed down to child component and available for rendering. In the below pic, App is parent component & HelloWorld is child component.

How can I create n divs, n coming from data in Vue?

This code doesnt create 4 divs. It just creates a div with 4 in it. How can I create divs by getting the number from data?
<template>
<div>
<div v-for="n in number" :key="n">
{{n}}
</div>
</div>
</template>
<script>
export default {
data: function () {
return{
number: 4
}
}
}
</script>
v-for can most certainly range over integers: https://v2.vuejs.org/v2/guide/list.html#v-for-with-a-Range
Here is your div example:
https://codesandbox.io/s/l7lr4q6qoz
If your version doesn't work your problem lies elsewhere, not with v-for="n in 5".

Do I always need to wrap the component content in a div (or similar)?

I'm creating single file components in Vue2, and I'm including a child component:
Parent Component:
<template>
<div>
<my-component-2>
</my-component-2>
</div>
</template>
<script>
....
</script>
Child component (my-component-2):
<template>
<my-component-3>
</my-component-3>
</template>
<script>
....
</script>
Grandchild component (my-component-3):
<template>
<div v-for="(item, index) in items">
</div>
</template>
<script>
...
</script>
But my-component-3 is not "rendered", however if I wrap <my-component-3> in a div (like in the parent component calling my-component-2), then it works.
Is there a way to call a child component without wrapping it in any html tag?
The <template> of a component can only have one direct child node.
Since your my-component-3 component's root element used a v-for, it could not render, since it would have multiple child nodes.
You never need to wrap a component in any element when using it in another component's template.
Example Multiple Elements
<template>
<template v-if="anyKey">
<div> DIV 1 Content </div>
<div> DIV 2 Content </div>
</template>
</template>
export default {
name: 'MyComponent',
data() {
return {
anyKey: "Any Value"
}
},
// ...
}
HTML will render without wrapping element
<div> DIV 1 Content </div>
<div> DIV 2 Content </div>