Vue3 v-for Maximum recursive updates exceeded in component <Carousel> - javascript

New to Vue and JS. I have a vue page myLists which takes an array of lists (containing media IDs for a title) which I used to make axios API calls and build a carousel (using vue3-carousel package) in the child with the return data sent as a prop. I'm currently dealing with a "Maximum recursive updates exceeded in v-for component " warning that I believe has to do with how I make my API calls. Here is the relevant code below:
Parent "myLists", has multiple lists (each list has movies) and fetches data from api using axios:
<template>
<div v-if="isReady">
<List v-for="list of theList" :key="list.id" :aList="list"></List>
</div>
</template>
export default {
setup() {
const theList = ref([]);
for (var myList of myLists) {
const aList = ref([]);
for (var ids of myList) {
var apiLink = partLink + ids;
axios.get(apiLink).then((response) => {
aList.value.push({
title: response.data.original_title || response.data.name,
overview: response.data.overview,
url: "https://image.tmdb.org/t/p/w500" + response.data.poster_path,
year: response.data.first_air_date || response.data.release_date,
type: ids[1],
id: response.data.id,
});
});
}
theList.value.push(aList.value);
}
return { theList };
},
computed: {
isReady() {
//make sure all lists are loaded from the api before rendering
return this.theList.length == myLists.length;
},
},
Child component "List" (not including script tag as I don't think it's too relevant) takes the fetched data as a prop and builds a carousel with it:
<template>
<Carousel :itemsToShow="4.5" :wrapAround="true" :breakpoints="breakpoints">
<Slide v-for="slide of aList" :key="slide">
<img
#click="showDetails"
class="carousel__item"
:src="slide.url"
alt="link not working"
:id="slide"
/>
</Slide>
<template #addons>
<Navigation />
<Pagination />
</template>
</Carousel>
</template>
Don't know what's causing the error exactly. I have a feeling it could be how I do all my API calls, or maybe it's something else obvious. Anyone have a clue?

The fix was actually absurdly simple, I was on the right track. The component was trying to render before my data was ready, so simply adding a
"v-if="list.length!==0"
directly on my Carousel component (within my List component), as opposed to the parent, fixed my issue.
I assumed that props are automatically ready when passed to a child if I used a v-if in my parent, turns out I was wrong, and that there's a delay.
Found the solution on the github issues page:
https://github.com/ismail9k/vue3-carousel/issues/154

Related

Vue JS: API call request on app.vue in single page application

in my project i'm suffering from delay in API requests and that's because i have huge amount of data in API, so i added a cache but it still appears white page when page creates, so i was thinking of adding API call on app.vue so request will be faster... is there a way to do it?
now i'm making API request on each page in my project
code below:
//building.vue where buildings api used
<template>
<b-row class="icon-examples">
<b-col lg="3" md="6" v-for="(building, index) in buildings"
:key="index" >
<button type="button" class="btn-icon-clipboard" #click="GoToBuilding(building._id)"
>
<div>
<i class="ni ni-building"></i>
<router-link
class="question-routerToDetail"
:to="`/tables/${building._id}`"
> <span > B info - </span>
<span>{{building.building_number}}</span></router-link>
</div>
</button>
</b-col>
</b-row>
</template>
<script>
import BuildingsService from "../../../services/ApiService"
export default {
data() {
return {
};
},
components: {
props:
['buildings'],
BaseHeader,
// buildings:[]
},
}
}
</script>
app.vue:
<template>
<router-view :number="count" #send="getNewCount" #reset="onReset" :buildings="buildings">
<sidebar :number="count" #reset="onReset"/>
</router-view>
</template>
<script>
export default {
components: {
sidebar,
},
data() {
return {
buildings: []
};
},
created(){
BuildingsService.getBuildings().then((response) => {
this.buildings = response.data.response;
console.log(this.buildings , "skk")
});
}
}
</script>
can i add saved API request array in app.vue and use it on other pages? and is it gonna improve my API call?
thanks in advance
Edit: updated question depending on answer below.. but getting empty data with no console errors
Yes, you can call the API a single time in the component that contains router-view and pass down the results to rendered components through props. The rendered components must declare the props that will be passed down to them, but only if they will be using it.
<template>
<router-view :number="count"
#send="getNewCount" #reset="onReset"
:buildings="buildings">
<sidebar :number="count" #reset="onReset"/>
</router-view>
</template>
<script>
import BuildingsService from "../../../services/ApiService"
export default {
components: {
sidebar,
},
data() {
return {
buildings: []
};
},
created(){
BuildingsService.getBuildings().then((response) => {
this.buildings = response.data.response;
});
}
}
</script>
But if the API call returns a huge amount of data or is really slow, then I really suggest that you check into optimizing the backend queries of your API, paginating results, and loading them progressively with lazy load on scroll or something.
Also it would be good if you can keep something in your state to check if the data is being loaded or not, so that you can show a loader or something rather than a white page until you get the results.
One possible solution is to adapt getBuildings(), such that it will call the backend only once and store the result in a property of BuildingsService, e.g. cachedBuildings.
If cachedBuildings is empty, getBuildings() calls the backend and stores the result in cachedBuildings.
If cachedBuildings is not empty, getBuildings() returns cachedBuildings.
The advantage of this solution is, that you only adapt your code in one place and the caching is transparent for every component.

How to return a component/component-tag dynamically in vue/nuxt?

I am trying to convert a next.js app(https://medium.com/strapi/how-to-create-pages-on-the-fly-with-dynamic-zone-8eebe64a2e1) to a nuxt.js app. In this app I can fetch data from an API and the next.js app uses the APIs data to generate new pages with its corresponding content. Works well in Next.js.
The data/content from the API consists of Seo data for the page, some static values and very important of blocks. These blocks have an attribute called __component where the components name is saved and also have the components data like images, text, etc. So I only have to deal with next.js when adding new components.
In next.js I used the catch-all-route ./pages/[[...slug]].js to catch any slug an user may enter. Then the API is fired with the slug from the context.query and I get back the pages data if it exists. Now the APIs json data only needs to be passed to the blockmanager component.
const Universals = ({blocks}) => {
return <div><BlockManager blocks={blocks}></BlockManager></div>;
};
Here the blockmanager gets the json list of blocks, from which to parse the components.
import Hero from '../../blocks/Hero';
import Pricing from '../../blocks/Pricing';
const getBlockComponent = ({__component, ...rest}, index) => {
let Block;
switch (__component) {
case 'blocks.hero':
Block = Hero;
break;
case "blocks.prices":
Block = Pricing;
break;
}
return Block ? <Block key={`index-${index}`} {...rest}/> : null;
};
const BlockManager = ({ blocks }) => {
return <div> {blocks.map(getBlockComponent)} </div>;
};
BlockManager.defaultProps = {
blocks: [],
};
export default BlockManager;
How can I replicate this line now in nuxt js?
return Block ? <Block key={`index-${index}`} {...rest}/> : null;
How to return a component/component-tag dynamically in vue/nuxt ?
Is there maybe another solution to automatically insert the wanted component?
Maybe someones knows ho to convert the blockmanagers logic to vue/nuxt logic entirely.
I think you're looking for the is attribute. You can read about it here.
Your template would look like:
<component
:is="__component"
key={`index-${index}`}
/>
Ok I think I got it. No strange stuff actually. I thought about it too complicated. Wanted all dynamically created but no need as I saw later ...
<template v-if="blocks">
<div id="example-1">
<div v-for="({__component, ...rest}=block, i) in blocks" :key="i">
<Hero :data="rest" v-if="__component === 'blocks.hero'"/>
<Pricing :data="rest" v-if="__component === 'blocks.pricing'"/>
</div>
</div>
</template>
<script>
import Hero from '../../blocks/Hero/Hero.vue';
import Pricing from '../../blocks/Pricing/Pricing.vue';
export default {
components: {
Hero, Pricing
},
props: {
blocks: Array
}
}
</script>

Vue 2 not setting component data when page is refreshed

I am having a rather strange problem with vue (v2.6.14) in which I am creating a new array based on one receive as a prop. Here's the relevant code:
props: { employees: Array },
data() {
return {
sortedEmployees: [],
};
},
mounted() {
this.sortedEmployees = this.employees.slice(0);
},
Essencially what I want here is to create a new reference for the employees array so that I can sort it to display later without actually altering the original array. I am not worried about cloning the actual objects inside the array since I will not alter them.
The problem with this is that, when the app 'hot reloads'(due to some change in the code) it works as expected, the hook is called and the component data is set according to what is expected BUT if I actually refresh the page in the browser, even though the hook is called, the component data is not set and I end up with an empty array. I can solve this by setting up a watcher to the prop and then it would set the data there, but what I am interested here is understanding what's happening. If the hook is called when the page is refreshed why doesn't it set the data properly as it does when 'hot reloading'?
I have a minimal setup created with vue-cli, no fancy configurations whatsoever. Any clue what I might be missing?
I guess the employees are loaded async, right?
I don't know your exact application structure, but the problem is usually the following:
The mounted hook gets called, when the component mounts, of course. If the employees are loaded async in the parent component, the mount hook is called, before the async call is resolved. So it will copy an empty array at this time.
With a watcher you solve this problem, because the watcher fires as soon as the async call resolves (as it mutates the employees array).
Same happens to the hotreload. When the hotreload occurs, the mounted hook gets executed again - at this time the employees array is already prefilled with values and therefore the correct array is copied in the mount hook.
Update
If you want to avoid watchers, you could also wrap your component like this:
<your-component
v-if="employees.length > 0"
:employees="employees"
/>
Be aware, that the copied employees array IN your component is still not reactive. It just copies the array on the first time it has more than 1 value. A watcher really makes sense in this case.
If you use watchers, make sure to use the immediate: true option. This ensures, that the watcher is also called the first time on render (and also on hotreload).
Summary
If you really want to copy the array, use watchers (with the immediate: true flag).
If it's just about sorting, go for the computed property solution suggested by #Sebastian Scholl
It sounds like the component is Refreshing (reseting) with the prop change however it's not Re-mounting. This is what would cause the array to reset to it's default state ([]), whereas on hot-reload the actual page is reloading.
Try the following, and if it doesn't resolver the issue I would suggest going the route of using a Watcher.
<template>
<div>
sorted: {{ sortedEmployees }}
<br />
not sorted: {{ employees }}
</div>
</template>
<script>
export default {
props: {
employees: Array,
},
data() {
return {
sortedEmployees: Array.from(this.employees),
};
}
};
</script>
Another approach is to just use a Computed method so and add any filtering/sorting logic inside that method. It would be something like:
<template>
<div>
<input v-model="sortKey" />
sorted: {{ sortedEmployees }}
</div>
</template>
<script>
export default {
props: {
employees: Array,
},
data () {
return {
sortKey: ''
}
},
computed: {
sortedEmployees() {
return Array.from(this.employees).sort(this.sortingFunction);
},
},
methods: {
sortingFunction(a, b) {
// sorting function using this.sortKey
}
}
};
</script>
UPDATED ANSWER
I switched up the code in the example a little and believe to have gotten it to work as I you're describing.
App.js
First off, I made it so that the employees array is updated after 3 seconds.
<template>
<div id="app">
<Dashboard :employees="employees" />
</div>
</template>
<script>
import Dashboard from './components/Dashboard.vue';
export default {
name: 'App',
components: { Dashboard },
data () {
return {
employees: [
{
employeeId: '1',
firstName: 'Leite',
}
]
};
},
methods: {
updateEmployees () {
this.employees = this.employees.concat([
{
employeeId: '2',
firstName: 'Jacinto',
},
{
employeeId: '3',
firstName: 'Capelo',
}
]);
}
},
mounted () {
setTimeout(this.updateEmployees, 3000)
},
};
</script>
Dashboard.js
The updated() lifecycle hook runs whenever data changes are detected (props and data properties). This effectively detected the change in the prop passed by the parent App.js component and re-rendered the data - whereas the mounted hook only ran once per page load.
<template>
<div id="dashbord">
<div
v-for="(employee, index) in sortedEmployees"
:key="employee.employeeId"
>
{{ employee.firstName }}
</div>
</div>
</template>
<script>
export default {
name: 'Dashboard',
props: {
employees: Array
},
data() {
return {
sortedEmployees: Array.from(this.employees)
};
},
updated() {
this.sortedEmployees = Array.from(this.employees)
}
};
</script>

Laravel & Inertia.js - reactivity problem (vue3)

could it be that Inertia.js page components are blocking the reactivity of vue?
I have a Page component, in this component is a normal single file component.
I have a function that adds items to the ItemsManager.items object.
When I'm running this function the single component below doesnt adds this items in the v-for.
But when I'm reload the Page Component it works and the previously added items appear.
Here the single file component:
<template>
<div>
<div v-for="item in items" :key="item.$key">
test
</div>
</div>
</template>
<script>
import { ItemsManager } from "./utils.js";
export default {
name: "test-component",
data: () => ({
items: ItemsManager.items
}),
};
</script>
utils.js:
export const ItemsManager = {
items: [],
add(item) {
item.$key = this.items.length;
this.items.unshift(item);
},
};
function that adds the items (in page component):
addItem(title, options) {
ItemsManager.add({
name: title,
options: options
});
},
Thanks in advance!
Since you're using Vue2, you need to know that there are some caveats when adding/deleting things to Objects/Arrays. You don't show any code relevant to your actual way of adding stuff to your object, but I can still recommend that you'd check this page to understand and fix your issue.
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

vue.js list ( template ) binding not updating when changing data from directive

First of all : I'm using laravel spark and the given setup of vue that comes with spark.
I have a "home" component with the prop "custom". Within custom there's a "passwords" array. (Entry added by code of directive, it's initialized empty)
My component ( alist) which should be bound against the data
<template id="passwords-list-template">
<div class="password" v-for="password in list">
<ul>
<li>{{ password.name }}</li>
<li>{{ password.description }}</li>
</ul>
</div>
</template>
<script>
export default {
template: '#passwords-list-template',
props: ['list'],
};
</script>
Usage
<passwords-list :list="custom.passwords"></passwords-list>
Using vue devtools I can see that my data is updating, however my list is not. Also other bindings like
<div v-show="custom.passwords.length > 0">
Are not working ...
UPDATE : Parent component (Home)
Vue.component('home', {
props: ['user', 'custom'],
ready : function() {
}
});
Usage
<home :user="user" :custom="spark.custom" inline-template>
Update 2: I played around a little bit using jsfiddle. It seems like changing the bound data object using $root works fine for me when using a method of a component. However it does not work when trying to access it using a directive
https://jsfiddle.net/wa21yho2/1/
There were a lot of errors in your Vue code. First of all, your components where isolated, there wasn't an explicit parent-child relationship.Second, there were errors in the scope of components, you were trying to set data of the parent in the child, also, you were trying to set the value of a prop, and props are by default readonly, you should have written a setter function or change them to data. And finally, I can't understand why were you trying to use a directive if there were methods and events involve?
Anyway, I rewrote your jsfiddle, I hope that you find what you need there. The chain is Root > Home > PasswordList. And the data is in the root but modified in home, the last component only show it. the key here are twoWay properties, otherwise you wouldn't be able to modify data through properties.
Here is a snippet of code
Home
var Home = Vue.component('home', {
props: {
user: {
default: ''
},
custom: {
twoWay: true
}
},
components: {
passwordList: PasswordList
},
methods: {
reset: function () {
this.custom.passwords = [];
}
}
});
// template
<home :custom.sync="spark.custom" inline-template>
{{custom | json}}
<button #click="reset">
reset in home
</button>
<password-list :list="custom.passwords"></password-list>
<password-list :list="custom.passwords"></password-list>
</home>
Here is the full jsfiddle

Categories