vuejs props Avoid mutating a prop directly - vue.js

my application is working fine, but here is the issue where I get an error, when I click on any of the menu, I get the following error, please help. good work.
[Vue warn]: 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: "selectedPost"
TabloStart.vue
<template>
<section class="section-content padding-y bg">
<div class="container">
<div class="row">
<TableMenu :posts="posts" :selectedPost="selectedPost"></TableMenu>
</div>
</div>
</section>
</template>
<script>
export default {
name: "TabloStart",
data() {
return {
posts: [
{
id: 1,
title: "Cat Ipsum",
content:
"<p>Dont wait for the storm to pass, dance in the rain kick up litter decide to want nothing to do with my owner today demand to be let outside at once, and expect owner to wait for me as i think about it cat cat moo moo lick ears lick paws so make meme, make cute face but lick the other cats. Kitty poochy chase imaginary bugs, but stand in front of the computer screen. Sweet beast cat dog hate mouse eat string barf pillow no baths hate everything stare at guinea pigs. My left donut is missing, as is my right loved it, hated it, loved it, hated it scoot butt on the rug cat not kitten around</p>"
},
{
id: 2,
title: "Hipster Ipsum",
content:
"<p>Bushwick blue bottle scenester helvetica ugh, meh four loko. Put a bird on it lumbersexual franzen shabby chic, street art knausgaard trust fund shaman scenester live-edge mixtape taxidermy viral yuccie succulents. Keytar poke bicycle rights, crucifix street art neutra air plant PBR&B hoodie plaid venmo. Tilde swag art party fanny pack vinyl letterpress venmo jean shorts offal mumblecore. Vice blog gentrify mlkshk tattooed occupy snackwave, hoodie craft beer next level migas 8-bit chartreuse. Trust fund food truck drinking vinegar gochujang.</p>"
},
{
id: 3,
title: "Cupcake Ipsum",
content:
"<p>Icing dessert soufflé lollipop chocolate bar sweet tart cake chupa chups. Soufflé marzipan jelly beans croissant toffee marzipan cupcake icing fruitcake. Muffin cake pudding soufflé wafer jelly bear claw sesame snaps marshmallow. Marzipan soufflé croissant lemon drops gingerbread sugar plum lemon drops apple pie gummies. Sweet roll donut oat cake toffee cake. Liquorice candy macaroon toffee cookie marzipan.</p>"
}
],
selectedPost: null
}
}
}
</script>
TableMenu.vue
<template>
<div class="posts-tab">
<ul class="nav nav-tabs">
<li v-for="post in posts"
v-bind:key="post.id"
v-on:click="selectedPost = post"
class="nav-item"
>
<a href="#" v-bind:class="{ active: post === selectedPost }" class="nav-link">
{{ post.title }}
</a>
</li>
</ul>
<div class="selected-post-container">
<div v-if="selectedPost"
class="selected-post">
<h3>{{ selectedPost.title }}</h3>
<div v-html="selectedPost.content"></div>
</div>
<strong v-else>
Click on a blog title to the left to view it.
</strong>
</div>
</div>
</template>
<script>
export default {
name: "TableMenu",
data() {
return {}
},
props: {
posts: [],
selectedPost: null,
}
}
</script>

v-on:click="selectedPost = post" is the culprit; selectedPost is a prop here and you cannot assign to a prop.
There are two different solutions depending on what you want:
Make selectedPost a local data property instead of a prop. You can then modify selectedPost but since it is no longer a prop, you cannot accept selectedPost from the parent anymore (but you're not really doing that anyway).
data() {
return {
selectedPost: null
}
}
Instead of modifying selectedPost directly, you should emit an event which the parent can handle to update its data. The convention is to emit an event named like update:selectedPost which will make it work with the .sync modifier.
Change the click handler to:
#click="$emit('update:selectedPost', post)"
Then in the parent, update as follows:
<TableMenu
:posts="posts"
:selectedPost="selectedPost"
#update:selectedPost="selectedPost = $event"
></TableMenu>
Or you can use .sync to make the above a bit simpler (it does the same thing):
<TableMenu
:posts="posts"
:selectedPost.sync="selectedPost"
></TableMenu>

Related

How can I dynamically render an array passed as a prop in vue.js?

I am trying to pass this data to a component in vue. I can get the data in the child component, and can render the array, or access each object properties by calling products[0].name, but I am looking to render each object separately in a v-for loop. please help!!
parent component:
<template>
<div>
<h1>Welcome To Our Shop</h1>
<div class="products">
<div v-for="product in products" v-bind:key="product.name">
<div><ShopItem v-bind:products="products" /></div>
</div>
</div>
</div>
</template>
<script>
import ShopItem from "../components/Shop/ShopItem";
export default {
name: "Shop",
components: { ShopItem },
data() {
return {
products: [
{
name: "Basic Deck",
price: 7,
description:
"The Basic Deck includes 68 cards: 10 cards in each of six categories, three icon legend cards, five blank cards for developing your own backstory elements, and instructions.",
image: require("#/assets/Draeorc.png"),
},
{
name: "Card Bundle",
price: 10,
description:
"The Card Bundle includes the Basic Deck, Technical Booster, Mystical Booster and instructions as a single self-printable PDF.",
image: require("#/assets/Twilight.png"),
},
{
name: "Full Bundle with Box",
price: 12,
description:
"The Full Bundle includes the Basic Deck, Technical Booster, Mystical Booster, instructions and tuck box as a single self-printable PDF.",
image: require("#/assets/Orig_Godbringer.png"),
},
],
};
},
};
</script>
child component:
<template>
<div class="product-container">
<div>
<h2>{{ products[0].name }}</h2> //this is where I want to call on the name
<div class="card-container">
<img src="../../assets/Draeorc.png" alt="cards" />
</div>
</div>
</div>
</template>
<script>
export default {
name: "ShopItem",
props: ["products"],
};
</script>
Here
<div v-for="product in products" v-bind:key="product.name">
<div><ShopItem v-bind:products="products" /></div>
</div>
your code does not make sense why?
because you want to go through the array which is here products and
show each item inside the products array. When you go through the
array, an item which is right for that iteration will be passed to
ShopItem component and no need to access index by using
products[index]
so it is better to do the following
<div><ShopItem v-bind:product="product" /></div>
Therefore, your ShopItem component will have access to a product one at the time when it goes through the v-for loop
Change
v-bind:products="products"
to
v-bind:products="product"
since you are using for-of loop
and on child component, change:
products[0].name
to
products.name
and since the property is an object, not an array, it's better to change your property name to product instead of products
So you will have this on parent component:
<div v-for="product in products" v-bind:key="product.name">
<div><ShopItem :product="product" /></div>
// :product is a shorthand for v-bind:product
</div>
and this on child component:
<template>
<div class="product-container">
<div>
<h2>{{ product.name }}</h2> //this is where I want to call on the name
<div class="card-container">
<img src="../../assets/Draeorc.png" alt="cards" />
</div>
</div>
</div>
</template>
<script>
export default {
name: "ShopItem",
props: ["product"],
};
</script>

Why isn't the value of the object properties inserted here?

I started learning vue yesterday and I'm now fiddling around on the CLI3.
Currently I'm trying out the different approaches to inserting data into my markup.
Here, I basically want to make a "list of Lists".
This here is list1:
<template>
<div>
<ul v-for="item in items">
<li :text="item"></li>
</ul>
</div>
</template>
<script>
export default{
name:"list1",
data() {
return {
items: {
item1 : "itemA",
item2 : "itemB",
item3 : "itemC"
}
}
}
}
</script>
This is the list of lists:
<template>
<div>
<h1>All my stuff in a biiig list!</h1>
<listOfLists />
</div>
</template>
<script>
import listOfLists from '#/components/listOfLists.vue'
export default {
name: 'myComplexView.vue',
components: {
listOfLists
}
}
And this is inserted into myComplexView.vue inside views (im working with routing as well, though it doesnt work perfectly yet as you will see on the screenshot), which you can see here:
<template>
<div>
<h1>All my stuff in a biiig list!</h1>
<listOfLists />
</div>
</template>
<script>
import listOfLists from '#/components/listOfLists.vue'
export default {
name: 'myComplexView.vue',
components: {
listOfLists
}
}
</script>
This is the result Im getting:
https://imgur.com/H8BaR2X
Since routing doesnt work correctly yet, I had to enter the url into the browser manually. Fortunately, the site at least loaded that way as well, so I can tackle these problems bit by bit ^^
As you can see, the data was iterated over correctly by the v-for.
However, the data wasn't inserted in the text attribute of the li elements.
I'm a bit clueless about the cause though.
Maybe I'm not binding to the correct attribute? Vue is using its own naming conventions, based off standard html and jquery as far as I understood.
You've got this in your template:
<li :text="item"></li>
This will bind the text attribute to the value, outputting, e.g.:
<li text="itemA"></li>
You should be able to see this in the developer tools. In the picture you posted you hadn't expanded the relevant elements so the attributes can't be seen.
I assume what you want is to set the content. For that you'd either use v-text:
<li v-text="item"></li>
or more likely:
<li>{{ item }}</li>
Either of these will output:
<li>itemA</li>
On an unrelated note, I would add that this line will create multiple lists:
<ul v-for="item in items">
It's unclear if that's what you want. You're going to create 3 <ul> elements, each with a single <li> child. If you want to create a single <ul> then move the v-for onto the <li>.

Bootstrap Vue accordion not expanding with id from array

I have an array for a Bootstrap-Vue accordion that includes an id. The button appears with my text but will not expand on clicking. I'm fairly new to coding in general and this is my first week using Vue so super-new to it.
I've tried creating an id of "accordion-1", "accordion-2" etc. and feeding the v-b-toggle and collapse id "accordionItems.accId". I've also tried having the accId be a number (1, 2, etc.) and feeding the v-b-toggle and collapse ids as "'accordion1' + accId" and also +item.id and +"accordionItems.accId. Lastly I tried adding the reactive: true statement because I'm getting the message about reactivity in the Console but nothing works.
<b-card no-body class="mb-1" v-for="item in accordionItems" :key="item.accId">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button block v-b-toggle="'accordion-' + accordionItems.accId" variant="primary"> {{ item.button }}
</b-button>
</b-card-header>
<b-collapse id="'accordion-' + accordionItems.accId" accordion="my-accordion" role="tabpanel">
<b-card-body>
<b-card-text>{{ item.text }}</b-card-text>
</b-card-body>
</b-collapse>
</b-card>
<script>
export default {
name: "Accordion",
data() {
return { reactive: true,
accordionItems: [
{
accId: 1,
button: `Who played Burt and Heather Gummer in Tremors?`,
text: `Answer: Burt: Michael Gross, Heather: Reba McEntire`
},
{
accId: 2,
button: `In "The Bachelor and the Bobby-Soxer" (1947), what line does Dick Nugent (Cary Grant) use to convince Susan Turner (Shirley Temple) he's no more mature than boys her own age?`,
text: `Answer: "Mellow greetings, yookie dookie!"`
}
],
}
}
}
</script>
When I inspect with Vue, I can see the id is showing as "'accordion-' + accordionItems.accId" so I know I'm missing something. I'm expecting to click on the question and have it expand to show the answer.
You need to "bind" to ID:
<b-collapse :id="'accordion-' + accordionItems.accId" accordion="my-accordion" role="tabpanel">
<b-card-body>
<b-card-text>{{ item.text }}</b-card-text>
</b-card-body>
</b-collapse>
Note the : before id=. This instructs Vue that the value in the quotes is a javascript expression.
What was happening with id="'accordion-' + accordionItems.accId" is that Vue sees the value between the double quotes as a string literal, not a JavaScript Expression.

Use a Vue component for multiple templates

I'm fairly new to Vue and I'm not even sure if I've phrased my question correctly. Here is what I am trying to achieve. I am using a card cascade from bootstrap, each card show part of a blog post. Each card has a link to a webpage for the whole blog.
To try and achieve this I have two vue files. cardCascade.vue and singleBlog.vue. My problem is at the moment I have to create a different singleBlog.vue files for each blog I have.
For example, suppose I have two blog posts in my database. cardCascade.vue will also have two links to individual blog posts. Blog post 1 will then use singleBlog1.vue and blog post 2 will then use singleBlog2.vue
What can I do so that I can achieve this more efficiently such that I only need one singleBlog.vue and it dynamically adjusts the content based on the link I select from cardCascade.vue?
What I have right now for parts of the cardCascade.vue
<b-card v-for="blog in blogs_duplicate" title="Title" img-src="https://placekitten.com/500/350" img-alt="Image" img-top>
<b-card-text>
<!--{{getBlogOfType("Vue",blog.id)}}-->
{{getBlogOfType("Vue",blog.id)}}
</b-card-text>
<b-card-text class="small text-muted">Last updated 3 mins ago</b-card-text>
</b-card>
Below is what I have right now for singleBlog.vue, keep in mind right now it just displays all the blog posts in a list.
<template>
<div id="single-blog">
<ul>
<article>
<li v-for="blog in blogs" v-bind:key="blog.id">
<h1>{{blog.title}}</h1>
<i class="fa fa-cogs" aria-hidden="true"></i>
<router-link v-bind:to="{name:'datascience-single', params: {blog_id: blog.blog_id}}">
<p>{{blog.content}}</p>
</router-link>
</li>
</article>
</ul>
</div>
</template>
<script>
import db from './firebaseInit'
export default{
data(){
return{
id:this.$route.params.id,
blogs:[],
}
},
created(){
//this.$http.get('http://jsonplaceholder.typicode.com/posts/' + this.id).then(function(data){
//this.blog = data.body;
db.collection('Blogs').orderBy('Type').get().then(querySnapshot =>{
querySnapshot.forEach(doc => {
//console.log(doc.data());
const data={
'id': doc.id,
'content': doc.data().Content,
'type': doc.data().Type,
'title': doc.data().Title,
'blog_id':doc.data().blog_id,
}
this.blogs.push(data)
})
})
}
}
</script>
It seems as though you should be giving your common component as a props information as to what it is rendering. Meaning you would make the call to the api in the parent and then make your child a "dumb" component. In your case you should make the calls to the api in cardCascade and then pass into your singleBlog component an id as props. Although in your case I do not see you using this component in the parent at all, where is it?
Like Michael said, the right way to do that is making a props and receives that prop from your router. Then, when you make your requisition, you'll pass this prop id. You can read more about props in here: https://v2.vuejs.org/v2/guide/components-props.html

how to bind 2 classes at the same time?

I need to make background color blue if element is current (bg-blue-400), green background (bg-green-400) - completed, dark (bg-dark-400) - uncompleted (only one can be activated). Example what I want:
My code:
<div v-bind:class="{'bg-blue-400' : isSelected(task), 'bg-grey-400' : isNotCompleted(task)}"
class="icon-block s30 text-white" >{{index+1}}</div>
Result:
Put this kind of logic within your script tag, and not within your template.
Based on the type of behaviour you want you need to choice between putting your logic within a computed property or method. (ask yourself the question: do you want the css class to change when your task changes? if so use computed property otherwise use a method)
Another improvement (since you are using a v-for) is to put your individual tasks in component.
Tasks component:
<task v-for="task in tasks" :task="task"></task>
Task component:
<template>
<div v-bind:class="cssClass">
<p> {{ task.name }} </p>
</div>
</template>
<script>
computed: {
cssClass: function () {
return {
'bg-blue-400' if this.task.selected
}
}
}
</script>