You may have an infinite update loop in a component render function in vue for iteration - vue.js

I have an array with 4 objects, which I render as follows
<div v-for="item in cant_objetivos_tipo">
{{ item.__data__.nombre_monitor_tipo + ' : ' + item.__data__.cantidad_objetivos }}
</div>
then when i try fill another array with the data as follows
<div v-for="item in cant_objetivos_tipo">
{{ datapie.push(item.__data__.nombre_monitor_tipo + ' : ' + item.__data__.cantidad_objetivos) }}
</div
i get infinite loop error
datapie[] is declared before
<script>
import axios from 'axios'
export default {
data() {
return {
cant_objetivos_tipo: [],
datapie: [],
}
},...
I hope you can help me, thank you very much in advance

<template>
<div v-for="(item,index) in cant_objetivos_tipo" :key="index">
{{ item.__data__.nombre_monitor_tipo + ' : ' + item.__data__.cantidad_objetivos }}
</div
</template>
<script>
import axios from 'axios';
export default
{
name: 'MyCustomComponent',
data()
{
return {
cant_objetivos_tipo: [],
};
},
computed:
{
datapie()
{
return this.cant_objetivos_tip.map(item => item.__data__.nombre_monitor_tipo + ' : ' + item.__data__.cantidad_objetivos);
}
},
created()
{
this.fetchData();
}
methods:
{
fetchData()
{
axios.get('/api/get_tipo_objectivos').then(response =>
{
this.cant_objetivos_tipo = response || [];
});
}
}
}
</script>

Related

Losing my data when i refresh page in vuejs

I'm creating a social network for project in my formation, i have a like system and it work.
i have a components cardArticle with all info and i try to do a like count. It work but when i refresh the page or going on other page, i lost all my data likes (my data is not saved)
components/CardArticle.vue
<template>
<div id="card">
<div>
<a class="cardLink">
<img class="card-img" v-if="post.imageUrl !== undefined" :src="post.imageUrl" />
<h2 class="cardTitle"> {{ post.title }}</h2>
<p class="cardDescription"> {{ post.description }}</p>
</a>
</div>
<div class="buttonIcon">
<div>
<button type="button" class="buttonDelete" id="buttonDelete" #click="deletePost"
v-if="this.post.userId === this.user.userId || this.user.isAdmin === true">Supprimer</button>
<button type="button" class="buttonEdit" id="buttonEdit" #click="modifyPost"
v-if="this.post.userId === this.user.userId || this.user.isAdmin === true">
Editer
</button>
</div>
<div class="divIconLike">
<div class="iconLike">
<a #click="sendLike">
<i class="fa-regular fa-thumbs-up"></i>
</a>
</div>
<div class="countLike">
<p> {{ likes }} </p>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import router from "../router/index.js";
export default {
name: 'CardArticle',
data () {
return {
likes: 0
}
},
props: {
post: {
type: Object
}
},
computed: {
user() {
return this.$store.getters.user;
}
},
methods: {
sendLike() {
axios.post("http://localhost:3000/api/articles/" + this.post._id + "/like", {
userId: this.user.userId
}, {
headers: {
Authorization: "Bearer " + this.user.token
}
})
.then(response => this.likes = response.data.article.likes)
.catch(error => console.log(error))
}
}
}
</script>
views/home.vue
<template>
<div class="home" v-if="this.user.token !== null">
<CardArticle v-for="post in allPosts" v-bind:key="post.id" :post="post" />
</div>
</template>
<script>
import CardArticle from "../components/CardArticle.vue"
import axios from "axios";
export default {
name: 'HomeArticle',
data () {
return {
post: {
title: "",
description: "",
imageUrl: ""
},
allPosts: [],
}
},
computed: {
user() {
return this.$store.getters.user;
}
},
components: {
CardArticle,
},
mounted() {
axios.get("http://localhost:3000/api/articles", {
headers: {
Authorization: "Bearer " + this.user.token
}
})
.then(response => {
this.allPosts = response.data;
})
.catch(error => {
return error;
})
}
}
</script>
What i should do for not losing my data ?
I would not use vuex or localstorage for that if possible, you have idea ?
Thanks for your help
If you loading data from server, then refresh page, you always will be lose data, because browser loading page again from server, and application will load data again.
If you don't want use vuex (but why not?), you can write data to cookies (by setting cookie value), then load it on app startup (when page is loaded). But it's not best practice at all. You can use vue3-cookies lib (link).
By the way, better learn to use stores, most progressive, I think, is Pinia.
Check: https://pinia.vuejs.org/
i lost all my data likes (my data is not saved)
likes is belong to each articles and It should have been saved to your db and call API to retrieve it again on component mounting:
export default {
name: 'CardArticle',
data () {
return {
likes: 0 // It's not managed by component state
}
},
methods: {
sendLike() {
axios.post("http://localhost:3000/api/articles/" + this.post._id + "/like", {
userId: this.user.userId
}, {
headers: {
Authorization: "Bearer " + this.user.token
}
})
.then(
// invalidates, update allPosts props (emit to parent)
)
.catch(error => console.log(error))
}
}
}

Every view or component that uses pagination has 3 default properties. Best way to avoid duplicating these properties on every component?

Every one of my Vue.js components has the following 3 properties used to track the items:
data(){
return {
currentPage: 1,
totalItems: null,
totalPages: null,
}
}
Which means I literally copy-paste these 3 properties on every component which has to use pagination. I know this is definitely not the most optimal way to do this. I was wondering if I use should mixins for this, or maybe some other implementation?
<template lang="html">
<div class="home">
<Product v-for='product in products' :product='product'></Product>
<Pagination :currentPage="currentPage" :totalPages="totalPages" #setCurrentPage="setCurrentPage"></Pagination>
</div>
</template>
<script>
import axios from 'axios'
import Product from '../components/Product'
import Pagination from '../components/Pagination'
export default {
components: {
Product,
Pagination
},
data(){
return {
currentPage: 1,
products: null,
totalItems: null,
totalPages: null,
}
},
methods: {
async getProducts(){
let response = await axios.get('/products?page=' + this.currentPage)
this.products = response.data.products
this.totalItems = response.data.totalItems
this.totalPages = response.data.totalPages
},
setCurrentPage(page){
this.currentPage = page
}
},
mounted(){
this.getProducts()
}
}
</script>
Pagination component:
<template lang="html">
<div class="pagination">
<div v-for="page in pages">
<div class="page" :class="{ active: page == currentPage}" #click="setCurrentPage(page)">{{ page }}</div>
</div>
</div>
</template>
<script>
export default {
props: ["totalPages", "currentPage"],
methods: {
setCurrentPage(page){
this.$emit("setCurrentPage", page);
}
},
computed: {
pages() {
const numShown = Math.min(5, this.totalPages);
let first = this.currentPage - Math.floor(numShown / 2);
first = Math.max(first, 1);
first = Math.min(first, this.totalPages - numShown + 1);
return [...Array(numShown)].map((k,i) => i + first);
}
}
}
</script>

How to make a function from another brother component be called when pressed?

Good afternoon, tell me please, I wrote a calendar displaying the events that the user sets, and now I want to make a detailed display of the event when I click it. The problem is that the events are in my component and the whole display logic is in another, how can I use them together.I want to make it so that the getDetailInformation() function in the ModalWindowDetail component component is called by clicking on an event in another component Calendar.vue. I use the event bus, but nothing works for me, I don’t understand why. Please help me solve this problem.
Screenshot of Calendar and error in console
Code of Calendar on GitHub
App.vue:
<template>
<div class="all">
<app-calendar #sendTextEvent="text = $event"></app-calendar>
<app-detail v-if="modalWindowDetail"
:eventText="text"></app-detail>
</div>
</template>
<script>
import appCalendar from './components/Calendar.vue'
import appDetail from './components/ModalWindowDetail.vue'
export default {
data(){
return{
text: String
}
},
components: {
appCalendar,
appDetail
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
}
}
};
</script>
Calendar.vue component which display calendar:
<template>
<div class="overflow-div">
<transition :name="nameOfClass" >
<div :key="currentPage" class="fade_wrapper">
<div v-for="(week, i) in getCalendar" class="d_day">
<li v-for="day in week" class="li_day">
<div class="day">{{ day }}</div>
<div v-for="event in buildEvents(i, day)"
class="event"
v-bind:class="{ 'eventBrown': eventBrown(event),
'eventPurple': eventPurple(event),
'eventOrange': eventOrange(event),
'eventBlue': eventBlue(event) }"
v-on:click="openModalDetail(event)">{{ event }}
</div>
</li>
</div>
</div>
</transition>
</div>
</template>
<script>
import json from './Calendar_data.json'
import { mapState } from "vuex";
import { eventBus } from './../main.js'
export default {
mounted(){
eventBus.$on('getDetailInformation', this.openModalDetail())
},
computed: {
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
methods: {
openModalDetail(text){
this.$emit('sendTextEvent', text);
}
};
</script>
The component in which the getDetailInformation() is located ModalWindowDetail.vue:
<template>
<div class="underModalWindow">
<div class="modalWindow">
<img src="src/assets/x.png" width="20px" height="20px">
<div class="nameofModal">Вся детальная информация о событии</div>
<div v-for="(key, name) in eventDetail" class="detailEvent">{{ name }}: {{ key }}</div>
<button>Окей</button>
</div>
</div>
</template>
<script>
import { eventBus } from './../main.js'
export default {
props: ['eventText'],
data(){
return{
options: [
{ text: 'Встреча', value: '8' },
{ text: 'День Рождения', value: '4' },
{ text: 'Праздник', value: '1' },
{ text: 'Другое', value: '16' }
],
eventDetail: Object,
}
},
computed: {
eventsData() {
return this.$store.state.eventData;
},
modalWindowDetail() {
return this.$store.state.modalWindowDetail;
},
},
created(){
eventBus.$emit('getDetailInformation', this.getDetailInformation())
},
methods: {
getDetailInformation(){
let arrOfEvents = this.eventsData.events;
for(let z = 0; z < arrOfEvents.length; z++){
let memo = arrOfEvents[z].memo;
console.log(this.memo)
if(memo === this.eventText){
let dataStartOfEvent = arrOfEvents[z].starts_at;
let getStartDataOfEvent = new Date(dataStartOfEvent);
let dataEndOfEvent = arrOfEvents[z].ends_at;
let getEndDataOfEvent = new Date(dataEndOfEvent);
if((getStartDataOfEvent.getHours() - 3) > 0){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleTimeString(),
'Конец события': getEndDataOfEvent.toLocaleTimeString(),
'Тип события': this.getType(arrOfEvents[z].type)
}
}else if(getStartDataOfEvent.getDate() != getEndDataOfEvent.getDate()){
this.$store.commit('changeModalWindowDetail', this.modalWindowDetail);
this.eventDetail = {
'Событие': this.eventText,
'Начало события': getStartDataOfEvent.toLocaleDateString(),
'Конец события': getEndDataOfEvent.toLocaleDateString(),
'Тип События': this.getType(arrOfEvents[z].type)
}
}
}
}
}
}
};
</script>
You should remove the () from the function name in eventBus.$on('getDetailInformation', this.openModalDetail()) - you want to reference the function, not to call it and use the result as a reference.
Also, your function getDetailInformation() does not return anything - but you seem to expect that it returns a text. You should correct this.
And finally, I think that #sendTextEvent="text = arguments[0]" would be more appropriate - and using a dedicated method/function will be the best.

How to pass and change index of array in vue?

I have the gist of how to do this, but I'm a beginner in vue, and I'm struggling with how to put it together. I need Control.vue to update the index in Exhibitor.vue. I know I'll have an $emit event happening in Control when I click on the button to pass the index data to the parent, and I'd have to use props to pass data from Exhibitor to its children, but how? I can't understand how to pass the index of an array with my code.
Exhibitor.vue
<template>
<div id="exhibitor">
<section class="exhibitor_info">
<h1 class="exhibitor_name">{{ exhibitors[index].firstName }} {{ exhibitors[index].lastName }}</h1>
<h2>Tag Number: {{ exhibitors[index].tagNum }} <em>{{ exhibitors[index].species }}</em></h2>
</section>
<div class="frame"><img :src="getImgUrl(exhibitors[index].picture)" alt="Exhibitor-Picture" class="image"></div>
</div>
</template>
<script>
export default {
name: 'Exhibitor',
data() {
return {
exhibitors: [],
index: 0
}
},
created: function() {
this.fetchExhibitors();
},
methods: {
fetchExhibitors() {
let uri = 'http://localhost:8081/exhibitor'
this.axios.get(uri).then(response => {
this.exhibitors = response.data
})
},
getImgUrl: function(pic) {
return require('../assets/' + pic)
}
}
}
</script>
Display.vue
<template>
<div id="display">
<exhibitor></exhibitor>
<buyer></buyer>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Buyer from './Buyer.vue';
export default {
components: {
'exhibitor': Exhibitor,
'buyer': Buyer
}
}
</script>
Control.vue
<template>
<div id="control">
<display></display>
<button v-on:click="incrementLeft">Left</button>
<button v-on:click="incrementRight">Right</button>
</div>
</template>
<script>
import Exhibitor from './Exhibitor.vue';
import Display from './Display.vue';
export default{
props: ['exhibitors', 'buyers', 'index'],
data() {
return {
index: 0
}
},
methods: {
incrementRight: function() {
// Note that '%' operator in JS is remainder and NOT modulo
this.index = ++this.index % this.exhibitors.length
},
incrementLeft: function() {
// Note that '%' operator in JS is remainder and NOT modulo
if (this.index === 0) {
this.index = this.exhibitors.length - 1
} else {
this.index = --this.index % this.exhibitors.length
}
}
},
components: {
'display': Display
}
}
</script>
So you can get what you want to happen and there are two ways of making it happen that I can think of. First I will just clarify the terms relating to this because you seem to have them the wrong way around. Let's look at you tier structure which is like this:
Control.vue
contains: Display.vue
contains: Exhibitors.vue & Buyers.vue.
Therefore Control.vue is the parent of Display.vue which is the parent of Buyers.vue and Exhibitors.vue.
Anyway, What we need to do is control the array of exhibitors (and I guess buyers but you didn't include them in your code so I'll do likewise) which is in Exhibitors.vue from Control.Vue even though they don't have a direct parent child relationship. What I've done is set a prop that is passed to Display.vue which uses scoped slots to render the exhibitors from Exhibitors.Vue. Because the left and right buttons need to know when to stop going I have emitted the array length from Exhibitors.vue to Display.vue and again to Control.vue. It all works so heres some code.
//Control.vue
<template>
<div class="content">
<display v-on:finalLength="setIndexLimit" :i="index"></display>
<button #click="changeDown">Down</button>
<button #click="changeUp">Up</button>
<p>{{indLimit}}</p>
</div>
</template>
<script>
import Display from '#/components/Display'
export default {
components: {
Display
},
data: () => ({
index: 0,
indLimit: 0
}),
methods: {
changeUp() {
if (this.indLimit === this.index+1) {
this.index=0
}
else {
this.index ++
}
},
changeDown() {
if (this.index === 0) {
this.index = this.indLimit - 1
}
else {
this.index --
}
},
setIndexLimit(e) {
this.indLimit = e
}
}
}
</script>
and
//Display.vue
<template>
<div id="display">
<p>From Display</p>
<exhibitors v-on:ExLength="setLength">
<p>{{i}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].firstName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].lastName}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].tagNum}}</p>
</exhibitors>
<exhibitors>
<p slot-scope="{ exhibitors }">{{exhibitors[i].species}}</p>
</exhibitors>
</div>
</template>
<script>
import Child from '#/components/admin/Exhibitors'
export default {
components: {
Exhibitors
},
props: [
'i'
],
data: () => ({
exhibitLength: null
}),
methods: {
setLength(e) {
this.exhibitLength = e
this.$emit('finalLength',e)
}
}
}
</script>
And finally:
//Exhibitors.vue
<template>
<div>
<slot :exhibitors="exhibitors"><p>I'm the child component!</p></slot>
</div>
</template>
<script>
export default {
data: () => ({
exhibitors: [
{
firstName: 'Joe',
lastName: 'Burns',
tagNum: 1,
species: 'ant'
},
{
firstName: 'Tom',
lastName: 'Yorke',
tagNum: 2,
species: 'bee'
},
{
firstName: 'Flit',
lastName: 'Argmeno',
tagNum: 3,
species: 'giraffe'
}
],
}),
mounted: function () {
this.$nextTick(function () {
let length = this.exhibitors.length
this.$emit('ExLength', length)
})
}
}
</script>
So as I said all that works, you can click the buttons and it will loop through the array contents. You can style it how you want wherever you like. However, it is a bit messy with props and slots and emits and whatnot just to relay a single index number and it would be far easier in my opinion to have a vuex store where 'index' is stored as a state item and can be used and changed anywhere without all the above carry on.

Axios response happening after component is rendered

I have a parent component making an Ajax request using Axios, The response is then assigned to a variabled called 'carousel' and is then passed down to the child component.
In the child component on 'created()' I am assigning the passed prop 'carousel' to a new variable called 'slides'
Problem is when I do this is returns undefined and my thinking is the Axios query hasn't returned before this happens.
Is there a way to delay the axios request before the prop is passed and the child component always gets the expected response.
My code is below.
Parent
<template>
<div class='product-container'>
<home-carousel :carousel="carousel"></home-carousel>
<profiler></profiler>
<cta-sections :panels="panels"></cta-sections>
</div>
</template>
<script>
import api from '../api/Home'
import CtaSections from '../components/CtaSections'
import HomeCarousel from '../components/HomeCarousel'
import Profiler from '../components/Profiler'
export default {
components: {
CtaSections,
HomeCarousel,
Profiler,
},
data() {
return {
panels: [],
slides: 'test',
carouselPass: [],
carousel: [],
}
},
created() {
axios.get(window.SETTINGS.API_BASE_PATH + 'pages/5')
.then(response => {
this.panels = response.data.acf.split_panels;
this.carousel = response.data.acf.carousel;
this.carousel.forEach(function (item, index) {
if (index === 0) {
item.active = true;
item.opacity = 1;
} else {
item.active = false;
item.opacity = 0;
}
item.id = index
})
})
},
}
</script>
Child
<template>
<div class='slider'>
<transition-group class='carouse carousel--fullHeight carousel--gradient' tag="div" name="fade">
<div v-for="slide in slides"
class="carousel__slide"
v-bind:class="{ active: slide.active }"
:key="slide.id"
:style="{ 'background-image': 'url(' + slide.image.url + ')' }"
v-show="slide.active"
>
<div class="carousel__caption carousel__caption--centered">
<h2 class="heading heading--white heading--uppercase heading--fixed">{{ slide.tagline }}</h2>
</div>
</div>
</transition-group>
<div class='carousel__controls carousel__controls--numbered carousel__controls--white carousel__controls--bottomRight carousel__controls--flex'>
<div #click="next" class="in">
<img src="/static/img/svg/next-arrow.svg" />
<span v-if="carousel.length < 10">0</span>
<span>{{ slideCount }}</span>
<span>/</span>
<span v-if="carousel.length < 10">0</span>
<span>{{ carousel.length }}</span>
</div>
</div>
</div>
</template>
<script>
import bus from '../bus'
import Booking from './Booking'
export default {
name: 'HomeCarousel',
props: ['carousel'],
data() {
return {
slideCount: 1,
slides: [],
/*
slides: [{
image: this.themepath + 'home-banner.jpg',
active: true,
captionText: 'A PLACE AS UNIQUE AS YOU ARE',
buttonText: 'book now',
buttonUrl: '#',
opacity: 1,
id: 1
},
{
image: this.themepath + 'home-banner2.jpg',
active: false,
captionText: 'A PLACE AS UNIQUE AS YOU ARE',
buttonText: 'book now',
buttonUrl: '#',
opacity: 0,
id: 2
}
]
*/
}
},
methods: {
showBooking: function() {
this.$store.state.showBooking = true;
},
next() {
const first = this.slides.shift();
this.slides = this.slides.concat(first)
first.active = false;
this.slides[0].active = true;
if (this.slideCount === this.slides.length) {
this.slideCount = 1;
} else {
this.slideCount++;
}
},
previous() {
const last = this.slides.pop()
this.slides = [last].concat(this.slides)
// Loop through Array and set all active values to false;
var slideLength = this.slides.length;
for (var slide = 0; slide < slideLength; slide++) {
this.slides[slide].active = false;
}
// Apply active class to first slide
this.slides[0].active = true;
this.slideCount--;
},
loopInterval() {
let self = this;
setInterval(function () {
self.next()
}, 8000);
}
},
created() {
this.slides = this.carousel;
}
}
</script>
You can just watch the prop and set this.slides when it changes, i.e. when the async call has finished:
watch:{
carousel(value) {
this.slides = value
}
}
Here's a JSFiddle: https://jsfiddle.net/nwLh0d4w/