How to render a v-for based on changing props? - javascript

Maybe I have a missconcept based on React.js, but I am working wih Vue.js
I Just create a component:
<ResultCards v-bind:cards="cards"/>
cards is updated when an event is triggered by other component:
methods: {
fillResultCards(cards){
this.cards = cards;
}
}
This is my ResultCard Component
<template>
<div>
<v-card
v-for="card in cards"
v-bind:key="card.id"
>
// ..card detail here
</v-card>
</div>
</template>
<script>
export default {
name: 'ResultCards.vue',
props: {
cards: Array,
},
};
</script>
But when I update the "cards" props send me the message:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "cards"
I tried to copy cards prop to a internal dat
data() {
return { internalCards: this.cards };
}
but it doesn't works.

Okay, it works, maibe I had a typo, but this is the FULL flow.
I have external Component (a search bar)
<SearchGit v-on:item-selected="fillResultCards"/>
when search concept fill the input, it emit the event item-selected passing results (from API) as args.
this.$emit('item-selected', this.cache.filter(item => item.full_name === textSearch));
In Container I set my data with the function
data: () => ({
cards: [],
}),
methods: {
fillResultCards(cards){
this.cards = cards;
}
}
then this "new version" of cards pass as prop to the render component
<ResultCards v-bind:cards="cards"/>
And the render component only need to render data, and thats all:
<v-card
v-for="card in cards"
v-bind:key="card.id"
>
<v-list-item three-line>
<v-list-item-content>
<div class="overline mb-4">{{ card.name }}</div>
<v-list-item-title class="headline mb-1">{{
card.full_name
}}</v-list-item-title>
<v-list-item-subtitle>{{ card.description }}</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-avatar tile size="80" color="grey">
<img v-bind:src="card.owner.avatar_url" alt="avatar" />
</v-list-item-avatar>
</v-list-item>
</v-card>
I don't change any of the code, but maybe a cache or something keeps old changes, it works now.

Related

How to put selected list values inside array in vue

I have a vue application where I have to select two elements from an list component and then put them inside an array.Right now I have my list and I can select them thanks to vuetify I binded it to an array with v-model I can console log it inside of an array but what I want to achieve is something like this:
{
"meetingName":"",
"meetingUrl":"",
"participants":{
participant1: "Hasan",
participant2: "Turan"
}
}
instead I am getting right now this:
{
"meetingName":"",
"meetingUrl":"",
"participants":[
"Hasan",
"Turan"
]
}
Could someone look at my code and tell me what is wrong with it?
html:
<template>
<v-container>
<v-row>
<v-col cols="4">
<v-card>
<v-list>
<v-list-item-group
v-model="model"
multiple
color="indigo"
v-model="model.participants"
>
<v-list-item
v-for="(item, i) in voterArrayFilteredByTime"
:key="item.voterUniqueName"
:value="item.voterUniqueName"
v-on:click= "generateGroup"
>
<v-list-item-content>
<v-list-item-title v-text="item.voterUniqueName"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-col>
</v-container>
</template>
an here is the method with which I want to console log it but it does not work like I said I am just getting numbers.
<script>
import axios from "axios";
export default {
name: "AddGroupsModal",
data : ()=>({
singleSelect: false,
selection: "",
model:{ meetingName: "", meetingUrl: "", participants: [] },
methods: {
generateGroup(){
console.log(this.model)
}
}
</script>
One of the problems in your markup is it has two bindings to v-model when there should be just one (the last one in this case):
<v-list-item-group
v-model="model"❌
multiple
color="indigo"
v-model="model.participants"❌
>
The v-list components can't create the expected value format, but you can use a computed prop along with Array.prototype.reduce to create an object from the array entries:
export default {
computed: {
computedParticipants() {
return this.model.participants.reduce((obj, name, i) => {
obj[`participant${i + 1}`] = name
return obj
}, {})
// OR:
const obj = {}
this.model.participants.forEach((name, i) => {
obj[`participant${i + 1}`] = name
})
return obj
},
},
}
demo

How to watch an array props value changes?

Here is my main component.
I have an array and ı want to send it to my child component to fill necessary fields. I sent it with named infos prop.
<template>
<list-form :infos="infos"/>
</template>
<script>
export default {
data(){
return(){
infos:[
{name:'',surname:'',date:new Date().toISOString().substr(0, 10),menu:false},
{name:'',surname:'',date:new Date().toISOString().substr(0, 10),menu:false},
{name:'',surname:'',date:new Date().toISOString().substr(0, 10),menu:false},
{name:'',surname:'',date:new Date().toISOString().substr(0, 10),menu:false}
]
}
}
}
</script>
Here is my child component
I get my prop in here and i used inside v-for loop.
<template>
<v-row v-for="(info,i) in infos" :key="'list-item-'+i">
<v-col cols="6"><v-text-field v-model="info.name"/></v-col>
<v-col cols="6" > <v-text-field v-model="info.surName"/></v-col>
<v-col cols="6">
<v-menu v-model="info.menu"
:close-on-content-click="false"
transition="scale-transition"
offset-y
max-width="290px"
min-width="auto">
<template v-slot:activator="{ on, attrs }">
<v-text-field v-model="info.dateFormatted"
label="date"
placeholder="GG/AA/YYYY"
v-bind="attrs"
v-on="on"/>
</template>
<v-date-picker
v-model="info.date"
no-title
#input="info.menu = false"
/>
</v-menu>
</v-col>
</v-row>
</template>
export default {
props:{
infos:{
type:Array
}
},
watch: {
**//the problem is in here**
date() {
this.dateFormatted = this.formatDate(date)
},
},
methods: {
formatDate(date) {
if (!date) return null
const [year, month, day] = date.split('-')
return `${month}/${day}/${year}`
}
}
}
</script>
I want to take date changes. If date changed i want to change its format . but I think it has a different way to catch changes in props. I tried some ways for this stuation but ı could not find true way.
Normally ı can get changes inside watch but i cant take changes with this way
You don't need to watch prop changes in your child component. Just watch changes in your parent component by using watch (if you really need) and write some conditions in your child component.
When you change data in parent component Vue will update your child component so you don't need to watch it inside.

How to use controlled components in Vue to set values in parent component object

I know how to use controlled componenents to emit the value that is selected. For example,
// app-select.vue
<v-select :items="[1,2,3]" #change="$emit('input', $event)"></v-select>
// parent-component.vue
<app-select v-model="selectedValue" />
So changing the value of v-select should change selectedValue on the parent component.
But what if I have an object I'd like to have updated by a controlled component with several selectors:
parent-comp.vue
<template>
<filter-comp> v-model="filterObj"</filter-comp>
</template>
<script>
import FilterComp from './filtercomp'
export default {
components: {
FilterComp
},
data () {
return {
filterObj: {}
}
}
}
</script>
and a child with several inputs capable of emiting on input:
<template>
<v-select :items="filterOneItems" #change="$emit('input', $event)">></v-select>
<v-select :items="filterTwoItems" #change="$emit('input', $event)">></v-select>
</template>
And let's say it would be my goal to make it so that when v-select input is given, it is updated on the parent component like so:
filterObj: {
filterOne: 'value 1',
filterTwo: 'value 2'
}
Is there a way to get this to work?
I can think of two solutions. First solution, just use prop not two-way binding:
For parent-comp.vue
<filter-comp :obj="filterObj"></filter-comp>
And for filter-comp.vue
<template>
<div>
<v-select :items="items" v-model="obj.filterOne"></v-select>
<v-select :items="items" v-model="obj.filterTwo"></v-select>
</div>
</template>
...
props: {
obj: {
type: Object,
default: {}
}
}
...
JSFiddle
Second solution, use v-model as you described:
For filter-comp.vue
<template>
<div>
<v-select :items="items" #change="change('filterOne', $event)"></v-select>
<v-select :items="items" #change="change('filterTwo', $event)"></v-select>
</div>
</template>
...
methods: {
change(name, value) {
this.$emit('input', {
...this.value,
[name]: value
})
}
}
...
JSFiddle

Simple toggle function in Vue trigger: You may have an infinite update loop

I have read a few answers about infinite update loop and still don't understand the issue.
I still keep getting this error message:
[Vue warn]: You may have an infinite update loop in a component render function.
What is the correct Vue way to write simple toggle function? Looks like my approach is just wrong.
<template>
<v-content>
<v-container fluid fill-height>
<v-layout align-center justify-center>
<v-btn
color="normal"
:click="toggleLogin()"
>
{{login ? "Register" : "Login"}}
</v-btn>
</v-layout>
</v-container>
</v-content>
</template>
<script>
export default {
data: () => ({
login: true
}),
methods: {
toggleLogin: function() {
console.log(this.login)
this.login = !this.login
}
}
}
</script>
You should change data binding
:click="toggleLogin()"
to event handling:
#click="toggleLogin()"

passing a callback function via props to child component is undefined in child vue

I have a Vue component that passes a callback function to another child component via props. However, it is the only piece that is undefined in the child.
I have created a repo for this so the files can be looked at. In the file brDialog.vue, I am passing button to the function click(), which should have access to the buttons callback that was passed within props from App.vue, however it is undefined within brDialog while the other two things passed with it are present(label and data).
I'll post the brDialog file, and will post the others if needed, but figured it would be easier to link a repo than post all the different files. I'm a bit new to Vue, so possibly something I'm missing in the documentation.
If you run the repo and click the Form Test button in the header, this is where the issue is.
brDialog.vue
<template>
<v-container>
<v-layout row wrap>
<v-flex xs12>
<v-dialog
v-model="show"
width="500"
persistent
>
<v-card>
<v-card-title> {{ title }} </v-card-title>
<slot name="content"></slot>
<v-card-actions>
<v-btn
v-for="button in buttons"
:key="button.label"
small
#click.native="click(button)"
>
{{ button.label }}
</v-btn>
<v-btn
v-if="showCloseButton"
small
#click.native="closeDialog()"
>
{{ closeButtonLabel }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import { props } from './props.js'
export default {
name: 'brForm',
components: {
brTextField: () => import('#/controls/brTextField/brTextField.vue'),
brTextArea: () => import('#/controls/brTextArea/brTextArea.vue'),
brSelectList: () => import('#/controls/brSelectList/brSelectList.vue')
},
props: props,
data () {
return {
}
},
methods: {
async click (button) {
const response = await button.callback(button.data)
if (response.close) {
this.closeDialog()
}
},
closeDialog () {
this.$emit('close')
}
},
computed: {
}
}
</script>
<style>
</style>
Maybe this is something I'm missing with an $emit in Vue or something, but it seems it should be working. Can someone point out why the callback is undefined after being passed to brDialog?
callback is undefined because you define your data property (App.vue from your repo) with an arrow function and loose the Vue context on this:
data: () => {
return {
testingForm: {
//...
dialog: {
props: {
buttonCallback: this.testingFormSave, //<-- here
buttons: [
{
label: 'Save',
data: {},
callback: this.testingFormSave //<-- and here
}
]
}
}
}
}
},
To fix your issue, change data: () => {...} to data () {...}

Categories

Resources