vue 2 v-for not working after loading json with axios - laravel

I searched the internet for a solution but none did work so far.
I am having a Vue component where I want to load dropdown content afterwards. Since it does not work, I simplified the code such that it should only show me the elements (which are driver names).
The problem is, that the v-for seems not to work, as the elements are not created in the DOM.
Here goes the code:
<template>
<div class="list-group">
<a class="list-group-item" v-for="driver in drivers">
{{driver.name}}
</a>
</div>
</template>
<script>
export default {
name: "DriverComponent",
data: function (){
return {
drivers: [],
}
},
mounted(){
this.loadDrivers();
console.log(this.drivers);
},
methods: {
loadDrivers: function(){
axios.get('/api/drivers')
.then(
(response) => {
this.drivers = response.data.data;
console.log(this.drivers);
}
)
.catch(function(error){
console.log(error);
});
// console.log(this.drivers);
}
}
}
</script>
In my app.js:
require('./bootstrap');
window.Vue = require('vue').default;
Vue.component('driver-component', require('./components/DriverComponent.vue').default);
const drivers = new Vue({
el: '#driv',
});
And my html looks as follows:
<div id="driv">
<driver-component></driver-component>
</div>
As you can see, I added some logs, which look like this:
Image of console output
Interestingly, it should be the same array, but the first array is empty but the second has the right values in it.-> EDIT: clarified, thank you
to highlight the problem: i would expect a list like:
v-for created list
Instead, I get a blank page.
It works, if I initialize the drivers array with the data I get in json. However, since I load the data afterwards, it seems not to work
Thank you for your help!
BR
Johannes
EDIT:
I am using "axios": "^0.21" and the controller is:
public function index(){
return DriverResource::collection(Drivers::all());
}
this controller returns the array in a data field, therefore, I set the response.data.data (meaning two times data)
The backend returns:
{"data":[{"id":1,"name":"UPS"},{"id":2,"name":"Hermes"}]}

See the homepage for axios. Under the "Response Schema" section they provide what a response looks like. Specifically for data, it's
{
// `data` is the response that was provided by the server
data: {}
< ... omitted for brevity ... >
}
Please check again what your backend is returning or is supposed to return. Log the whole response you get.
The this.drivers = response.data.data; seems to be incorrect, however the this.drivers = response.data; should work.
You didn't say which version of axios you are using or show your configuration so I'm using the following for an example:
axios = ^0.21.1
vue-axios = ^3.2.4
Replaced "name" with "title" to match the response format below:
{
"title": "delectus aut autem",
< ... omitted for brevity ... >
},
--
loadDrivers: function(){
Vue.axios.get('https://jsonplaceholder.typicode.com/todos')
.then(
(response) => {
this.drivers = response.data; // <<-- works and many task titles are shown on page
// this.drivers = response.data.data; // <<-- DOES NOT WORK LIKE FOR YOU, blank page
console.log(this.drivers);
}
)
.catch(function(error){
console.log(error);
});
// console.log(this.drivers);
}

Related

Append components inside other component in Vue JS

I'm trying to make a simple "Load More" function for posts using Vue JS but when I try to append new posts, the previous ones are removed.
This is my PostWallComponent, which is supposed to hold all posts (<post-item-component>).
I fetch first 4 posts from the DB, store them in this.posts and then I send them using the v-for loop to <post-item-component>.
Then when someone clicks on the "More" button I call getPosts() function where I fetch another 4 posts from the DB. Here comes my problem - I store these new posts inside this.posts and I try to append them to the post container. They do append but the previous 4 get deleted from the container.
I think I know what is wrong - at line this.posts = response.data I replace old posts with new ones but I don't know how to append new ones without removing old ones. I tried to push() new posts to the array but that turned into a big mess (repetitive posts in the container).
<template>
<div class="container">
<div class="post_container">
<post-item-component v-for="post in this.posts"
v-bind:cuid="cuid"
v-bind:auid="auid"
v-bind:post="post"
v-bind:key="post.id">
</post-item-component>
<button type="button" #click="getPosts">More</button>
</div>
</div>
</template>
<script>
import PostItemComponent from "./PostItemComponent";
export default {
props: ['init_place', 'init_type', 'current_user_id', 'active_user'],
components: {
PostItemComponent
},
data() {
return {
place: this.init_place,
type: this.init_type,
cuid: this.current_user_id,
auid: this.active_user,
limit: 4,
offset: 0,
posts: [],
};
},
mounted() {
console.log('Component mounted.');
this.getPosts();
},
methods: {
getPosts() {
console.log('post');
axios.get('/p/fetch', {
params: {
place: this.place,
type: this.type,
cuid: this.cuid,
auid: this.auid,
offset: this.offset,
limit: this.limit,
}
})
.then((response) => {
console.log(response);
this.posts = response.data;
this.offset = this.limit;
this.limit += 4;
})
.catch(function (error) {
//currentObj.output = error;
});
}
}
}
</script>
In case someone wonders:
cuid is current user id = ID of user whose profile I opened
auid is active user ID = logged in user ID
<post-item-component> is just couple of divs displaying post header, body etc.
you could also use this.posts = this.posts.concat(response.data)
the problem is that the Array.push() method does not work with vue reactivity. For that you need to replace the whole array. As one proposed solution, you could use the spread operator to achieve this as so:
this.posts = [...this.posts, ...response.data];
This is replacing the whole array with a new array that is combining the old items with the fetched ones by spreading each of the array elements into the new array.
You can see an example here:
codesandbox example

How to pass an object from axios catch block to parent component with Vue.js

I am using Laravel 7 and Vue.js 2.
I want to pass an object of validation errors from the catch block of an axios call to a parent component but for some reasons it doesn't work.
This is the code of the axios call:
runReport: function() {
let self = this;
const url = "api/get_report?room="+this.formReport['room']+"&participant="+this.formReport['participant']+"&start="+this.formReport['start']+"&end="+this.formReport['end'];
axios.get(url)
.then((response) => {
console.log(response.data.data);
this.meetingsReport = response.data.data;
this.$emit('passMeetings', this.meetingsReport);
this.$emit('success');
this.errors = {};
})
.catch(function(error) {
console.log(error.response.data);
self.errors = error.response.data;
alert(self.errors);
self.$emit('failure');
self.$emit('passErrors', self.errors); //problem
console.log('call ended');
});
}
This is the code in the parent component:
<template>
<div>
<report-meeting #passMeetings="onPassMeetings" #failure="displayTable=false" #success="displayTable=true"></report-meeting>
<hr>
<validated-errors :errorsMeeting="errorsMeeting" #passErrors="onPassErrors" v-if="displayTable===false"></validated-errors>
<table-report :meetingsSelected="meetingsSelected" v-if="displayTable===true"></table-report>
</div>
</template>
<script>
import TableReport from "./TableReport.vue"
import ReportMeeting from "./ReportMeeting.vue"
import ValidatedErrors from "./ValidatedErrors.vue"
export default {
components: {
'table-report': TableReport,
'report-meeting': ReportMeeting,
'validated-errors': ValidatedErrors
},
mounted() {
console.log('Component mounted.');
},
data: function() {
return {
displayTable: false,
meetingsSelected: {},
errorsMeeting: {}
}
},
methods: {
onPassMeetings(value) {
console.log(value);
this.meetingsSelected = value;
},
onPassErrors(value) {
console.log('errors passed'); //never used
this.errorsMeeting = value;
}
}
}
</script>
In the console I visualize no errors (except an 422 Unprocessable Entity). The strange thing is that the first emit works (failure), but the second one doesn't work (passErrors).
In the parent function onPassErrors I put a console.log that is never used so I suppose that the function is never called.
Can help?
This is likely caused by an event name mismatch, which can occur when using in-DOM templates because HTML attributes are automatically lower-cased (#passErrors becomes #passerrors in the DOM).
When using the development build of Vue, you'd see a warning in the browser's console:
[Vue tip]: Event "passerrors" is emitted in component but the handler is registered for "passErrors". Note that HTML attributes are case-insensitive and you cannot use v-on to listen to camelCase events when using in-DOM templates. You should probably use "pass-errors" instead of "passErrors".
This is not a problem in single file components (demo 1) or string templates (demo 2), but if you must stick with in-DOM templates, custom event names are recommended to be kebab-case:
<!-- Parent.vue -->
<MyComponent #pass-errors="onPassEvent" />
// MyComponent.vue
runReport() {
this.$emit('pass-errors', /*...*/)
}
demo 3

Vue js function countSubcategories() returns [object Promise]

countSubcategories() function returns [object Promise] where it should return row counts of mapped subcategories.
This code is in vue.js & Laravel, Any suggestions on this?
<div v-for="(cat,index) in cats.data" :key="cat.id">
{{ countSubcategories(cat.id) }} // Here subcategories row counts should be displayed.
</div>
<script>
export default {
data() {
return {
cats: {},
childcounts: ""
};
},
created() {
this.getCategories();
},
methods: {
countSubcategories(id) {
return axios
.get("/api/user-permission-child-count/" + `${id}`)
.then(response => {
this.childcounts = response.data;
return response.data;
});
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => (this.cats = response.data));
}
}
};
</script>
As Aron stated in the previous answer as you are calling direct from the template the information is not ready when the template is rendered.
As far as I understood you need to run getCategories first so then you can fetch the rest of your data, right?
If that's the case I have a suggestion:
Send an array of cat ids to your back-end and there you could send back the list of subcategories you need, this and this one are good resources so read.
And instead of having 2 getCategories and countSubcategories you could "merge" then like this:
fetchCategoriesAndSubcategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => {
this.cats = response.data;
let catIds = this.cats.map(cat => (cat.id));
return this.countSubcategories(catIds) // dont forget to change your REST endpoint to manage receiving an array of ids
})
.then(response => {
this.childcounts = response.data
});
}
Promises allow you to return promises within and chain .then methods
So in your created() you could just call this.fetchCategoriesAndSubcategories passing the data you need. Also you can update your template by adding a v-if so it doesn't throw an error while the promise didn't finish loading. something like this:
<div v-if="childCounts" v-for="(subcategorie, index) in childCounts" :key="subcategorie.id">
{{ subcategorie }} // Here subcategories row counts should be displayed.
</div>
Hello!
Based on the provided information, it could be 2 things. First of all, you may try replacing:
return response.data;
with:
console.log(this.childcounts)
and look in the console if you have the correct information logged. If not, it may be the way you send the information from Laravel.
PS: More information may be needed to solve this. When are you triggering the 'countSubcategories' method?
I would do all the intial login in the component itself, and not call a function in template like that. It can drastically affect the performance of the app, since the function would be called on change detection. But first, you are getting [object Promise], since that is exactly what you return, a Promise.
So as already mentioned, I would do the login in the component and then display a property in template. So I suggest the following:
methods: {
countSubcategories(id) {
return axios.get("..." + id);
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
// or use async await pattern
axios.get("...").then(response => {
this.cats = response.data;
// gather all nested requests and perform in parallel
const reqs = this.cats.map(y => this.countSubcategories(y.id));
axios.all(reqs).then(y => {
// merge data
this.cats = this.cats.map((item, i) => {
return {...item, count: y[i].data}
})
});
});
}
}
Now you can display {{cat.count}} in template.
Here's a sample SANDBOX with similar setup.
This is happen 'cause you're trying to render a information who doesn't comeback yet...
Try to change this method inside created, make it async and don't call directly your method on HTML. Them you can render your variable this.childcounts.

Avoid fetching images everytime page load - vuejs

I'm building a page that show dynamically some photos in a feed like Instagram. I'm getting stuck trying to avoid everytime I load a page or I go into a photo's detail page and then go back, to do an API request to Laravel controller, so that means fetching data and images, losing the position of the page and starting on the top of the page.
My code:
Feed.vue
<template>
<div v-for="(image, index) in images" :key="index">
<v-img :src="image.path" class="image-masonry mini-cover" slot-scope="{ hover }"></v-img>
</div>
</template>
<script>
export default {
data() {
return {
images: []
}
},
mounted() {
this.getImagesHome();
},
methods: {
getImagesHome() {
this.axios.get('/api/images', {},
).then(response => {
this.images = response.data;
}).catch(error => {
console.log(error);
});
},
}
}
</script>
Edit:
I saw that keep-alive is primarily used to preserve component state or avoid re-rendering it. But i can't understand how to use it. I call my Feed.vue component in another Home.vue as below:
<template>
<v-app>
<Toolbar #toggle-drawer="$refs.drawer.drawer = !$refs.drawer.drawer"></Toolbar>
<Navbar ref="drawer"></Navbar>
<keep-alive>
<Feed></Feed>
</keep-alive>
</v-app>
</template>
<script>
import store from '../store';
export default {
components: {
'Toolbar' : () => import('./template/Toolbar.vue'),
'Navbar' : () => import('./template/Navbar.vue'),
'Feed' : () => import('./Feed.vue')
}
}
</script>
What i have to put more in keep-alive and what i have to change in my Feed.vue component?
mounted() should only be called once.
There seem to be multiple ways to go about this.
If you are using vue-router, then have a look at scrollBehaviour
https://router.vuejs.org/guide/advanced/scroll-behavior.html
From their documentation,
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return desired position
}
})
You can also try using keep-alive: https://v2.vuejs.org/v2/api/#keep-alive
It keeps the component in memory so it is not destroyed, you get activated and deactivated events to check when it comes into view.
But I don't think it saves scroll position, so you may want to use this in combination with scrollBehaviour

{{ obj.data }} not displaying in vue template after axios get

I am trying to build a carousel using Laravel 5.6 and Vue.js. I can call the data from db, and console.log response.data. It works like expect it to. Script below.
<script>
$(document).ready(function() {
$("#availability").owlCarousel();
});
export default {
props: {
areaId: null,
tutorId: null,
},
data () {
return {
availability: []
}
},
methods: {
getAvailability () {
var that = this;
axios.get( '/' + this.areaId + '/' + this.tutorId + '/availability').then((response) => {
console.log(response.data)
that.availability = response.data;
});
}
},
mounted () {
this.getAvailability();
}
}
</script>
Now I would expect to be able to display the data like
{{availability.monday_begin}}
but it displays nothing, empty tags.
When I add a second .data to response, so change
that.availability = response.data;
to
that.availability = response.data.data;
The data object shows up as undefined in the vue-dev tools, and {{ availability.monday_begin }} throws an error cannot read property of undefined.
I also tried adding a v-if to the template, based on a couple different articles I read, but nothing I tried worked.
Thanks for your help.

Resources