Vue.js - repeat an element a specific number of times - javascript

I would like to use Vue.js to repeat an element n times, based on the num-labels prop.
<label-parent num-labels="4"></label-parent>
This is my component:
<template>
<div id="label-parent">
<div v-for="idx in numLabels">
{{idx}}
</div>
</div>
</template>
<script>;
export default {
name: "LabelParent",
props: ['numLabels'],
}
</script>
This code only outputs an empty div: <div id="label-parent"></div>. What is the correct way of doing this?

v-for can also take an integer. In this case it will repeat the template that many times.
<div v-for="n in 4">{{n}}</div>
and you will need to use v-bind:num-labels="4" to parse the value as number.
Vue Docs

<label-parent num-labels="4"></label-parent>
Passes 4 as a string which won't work with v-for.
Pass your argument with : to evaluate the expression so you get an actual number which will work with v-for.
<label-parent :num-labels="4"></label-parent>
BTW:
I highly suggest typing your props.
You can type your props like this:
export default {
props: {
numLabels: {
type: Number, // type of the property
required: (true|false), // is this prop required or not?
default: 0 // default value for this prop
}
}
}
See Prop Validation

As some people notice, this is because you're passing a 4 as string not integer
This should work
<label-parent :num-labels="4"></label-parent>
And this is the docs reference
Components Literal-vs-Dynamic

Related

Render an array of components in React/TypeScript?

I'm currently trying to render an array of JSX Components. I see two possible ways for me to do this.
One(current approach): Make the array of type object that holds the properties of the component and then construct the JSX component in line using the array contents as props
const todos = { title:string,content:string }[];
todos.map((todo:{ title:string,content:string })=>{<TitleCard title={todo.title}/>;
Two: Make the array of type JSX elements so the array stores the pure JSX Element and just plops it down with a .map inside a render function.
I find the first approach to be more clean, but it requires a bit more work in terms of constructing the JSX elements from the props in the array.
Which approach would be the correct/best way to go about this? Appreciate the help.
I'm not sure if I understand your second approach but first one sounds good. I cleaned the code up a bit and this is the way to go:
type Todo = {
title:string,
content: string
}
// this data(todos) probably will come from your props but I added some
// dummy data for demonstration purpose
const todos: Todo[] = [
{title:"title1", content:"content1"},
{title:"title2",content:"content2"}
];
const mappedTodosToRender = todos.map(({title})=>{<TitleCard key={title} title={title}/>;
return (
<>
{mappedTodosToRender}
</>
)

How to prevent the push method from overwriting my items in an array?

I'm studying Typescript and VueJS, and I need to push my array, but it ends up overriding my 'name' property.
This is my code:
const itemsSelectedOptions = ref([])
const nameTemplate = ref('')
function setItem(){
//#ts-ignore
itemsSelectedOptions.value.push({name: nameTemplate, fields: [...activities.value.selectedFields]})
}
<template>
<div>
<input v-model="nameTemplate" />
<button #click="setItem">Save Template</button>
<div>
</template>
Everything goes fine on my "fields" property, it doesn't overwrite. But in name always prevails the last value typed in "nameTemplate".
Anyway, I'll explain what "selectedFields" would be, they are boxes marked through a select.
As you mentioned, push overwrites the array, use it instead "concat"
Also, I think this link will be helpful.
Vue has issues with push/concats sometimes. I generally overwrite with destructuring.
itemsSelectedOptions.value = [
...itemsSelectedOptions.value,
{name: nameTemplate, fields: [...activities.value.selectedFields]}
]

Accessing and modyfing slot's elements in Vue 3

I'm rewriting my MQTT based dashboard from Vue 2 to Vue 3 currently, and can't solve one problem.
The dashboard has many Vue components, which reacts to specific MQTT topics and values, to display current system state. One of them is the mqtt-multi-state component which is declared like below:
// status-page
<mqtt-multi-state subscribe-topic="home/env/sensor/wc/door/status" json-path="state">
<div value="OPEN"><font-awesome-icon icon="door-open"/></div>
<div value="CLOSED"><font-awesome-icon icon="door-closed"/></div>
</mqtt-multi-state>
It contains dwo div elements, with two states that the system sensor (door) can has. These elements are passed to the default slot and are hidden by default via css.
What I want to achieve is to show one of them based on the equality of the value attr in each of them with the current MQTT value. So if the current value is OPEN then the first div show up, when value is CLOSED then the second one appears.
// mqtt-multi-state
<template>
<div class="mqtt-multi-state">
<div>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
methods: {
messageArrived(value){
let states = this.$slots.default() // here are the two divs
for(let i = 0;i < states.length;i++ ){
if(states[i].props.value === value )
{
//states[i].elm.className = "state-active" <- Vue 2 solution using DOM with elm
//states[i].props.class = "state-active"; <- Vue 3, doesn't work, not reactive?
}
else
{
//states[i].props.class = "";
}
}
}
}
}
</script>
I know, this approach is a little bit different but I really like to describe dashboard element in this way in HTML. In the messsageArrive() method I'm iterating over the default slot children elements and if the value match the current value prop I want to show this item, by add a state-active class. But this solution does not work. The VNode is changed (checked in the console).
In Vue 2 I've simply get to the DOM element directly and change it class, but on Vue 3 I can't figure it out, how to get from VNode to the DOM Node. Or there are maybe an other/better way to do that?
Well, many evenings later I came to this solution. Instead of putting all the different states into a default slot, I've created dynamic named slots.
The different state elements (font-awesome icons in this case) go to each slot's template element. Unfortunately I can't pass the mqtt value to the template element itself, because it does not exists inside mqtt-multi-state parent component. Instead, I've passed the mqtt value into the template's first child element. It can be any element, div, p, font-awesome-icon.
// status-page
<mqtt-multi-state :state-count="2" subscribe-topic="home/env/sensor/wc/door/status" json-path="state">
<template #0><font-awesome-icon value="OPEN" icon="door-open"/></template>
<template #1><font-awesome-icon value="CLOSED" icon="door-closed"/></template>
</mqtt-multi-state>
There is also a prop state-count that defines the number of different states. Note the colon before prop name, so the string "2" is a Number type.
The mqtt-multi-state component becomes like this:
// mqtt-multi-state
<template>
<div class="mqtt-multi-state">
<template v-for="(slot, index) in slots">
<slot :name="index" v-if="slot" ></slot>
</template>
</div>
</template>
<script>
export default {
data: function(){
return {
slots: []
}
},
props: {
stateCount: {
default: 2,
type: Number
}
},
methods: {
messageArrived(value){
for(let i = 0; i < this.stateCount; i++ ) {
this.slots[i] = this.$slots[i]?.()[0]?.props?.value === value
}
}
}
}
</script>
In the messageArrived() function the code iterates through all the slots and compares the slot's first child element prop, named value with the current mqtt value received. If the values are equal then the this.slots[i] array value goes true, and the Vue's v-if directive makes corresponding slot visible, otherwise hides the slot.
Named slots names are created by the index value, so to get their content with this.$slot.slotNameRenderFunction() I call the function 0(), 1() etc using the this.$slot[index]() notation (accessing object member by name stored in the variable) with an optional chaining operator ?. - so the whole notation looks a little weird :).

Render object in React

I want to render Object "genres" but my console returns to me :
Error: Objects are not valid as a React child (found: object with keys {genre}).
If you meant to render a collection of children, use an array instead.
My code
function Movie({title,summary,year,poster,genres}){
return(
<div className="movie">
{genres.map(genre=>{return {genre}})} //doesn't work
{genres.map(genre=>{return <li>{genre}</li>})}
</div>
)
}
the first code
{genres.map(genre=>{returns {genre}})}
doesn't work
But the second code below works well.
{genres.map(genre=>{returns <li>{genre}</li>})}
What's the difference between these two things?
This line
{genres.map(genre=>{returns {genre}})}
returns an object, out of each item in the array: { genre: 'drama' }
However, this other line
{genres.map(genre=>{returns <li>{genre}</li>})}
The { inside the JSX for li won't turn it into an Object, but will access its value: <li>drama</li>
The first code returns an object with key "genre" which its value is the genere value, and react can't render it.
The second one return the value of genre, because the braces here aren't an object braces but the correct syntax of putting variables inside html in react.
If you want to return the value without li, do: {genres.map(genre=> genre)}

Disable a button in angular depending on value of property of object inside an array?

I have an array of object:
keyDisabledArray=[
{'a':false},
{'b':true}
]
and some buttons
<button class="key" (click)="keyPressed('a')" id="a">A</button>
<button class="key" (click)="keyPressed('b')" id="b">B</button>
that must be disabled according to the content of keyDisabledArray. In this case, button 'b' must be disabled and 'a' enabled. I'm trying to use [disabled] but I'm not sure how to access the correct value in the array of objects.
EDIT: I found some ways to access the value, but all were asynchronous or demanding a full loop on the array to find the correct element, and in both cases, I see problems: how angular deal with asynchronous code in front end (?) and if it is possible to avoid a full loop on the array for every button.
You can write a pipe to extra the correct value.
Edit: see how to here to create a pipe which accepts multiple arguments How do I call an Angular 2 pipe with multiple arguments?
Edit #2: here is a sample piece of code (not tested)
import {Pipe, PipeTransform} from '#angular/core';
#Pipe({
name: 'isDisabled'
})
export class IsDisabledPipe implements PipeTransform
{
transform(array: any, key: string): any
{
let val = array.find(v => v.hasOwnProperty(key));
return val? val[key] : false;
}
}
you can then use it like
<button class="key" [disabled]="keyDisabledArray | isDisabled:'a'" (click)="keyPressed('a')" id="a">A</button>
Otherwise, why don't you convert your array to a json object?
keyDisabledArray=
{
'a':false,
'b':true
};
then, accessing the value will be easier
<button class="key" [disabled]="keyDisabledArray['a']" (click)="keyPressed('a')" id="a">A</button>

Categories

Resources