Avoid fetching images everytime page load - vuejs - image

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

Related

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

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);
}

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

VueJS: How do I initialise data so it shows in my template on component load

This is driving me nuts!
//ProfilePage.vue
<template>
<div>
<p>{{ this.$data.profile.desc }}</p>
<profileImage v-bind:profile="profile"></profileImage>
<profileText v-bind:profile="profile" v-on:updateData="updateDesc"></profileText>
</div>
</template>
<script>
import profileText from './ProfileText.vue';
import profileImage from './ProfileImage.vue';
export default {
name: 'profilePage',
component: {
profileText,
profileImage
},
data() {
return {
profile: {
image: '',
desc: ''
}
}
},
created() {
this.fetchProfile();
},
methods: {
async fetchProfile() {
const uri = 'http://localhost:8000/api/......get';
const response = await axios.get(uri);
.then(response => this.updateProfileData(response.data))
},
updateProfileData(data) {
this.$data.profile.image = data['image'];
this.$data.profile.desc = data['description'];
},
updateDesc(data) {
this.$data.profile.desc = data.desc;
},
}
}
</script>
<style scoped>
</style>
In the above .vue file. I execute a fetch to the back end which successfully returns the correct data from the DB. I successfully save the data returned to the data() part of the file. Next I import a component (the code for which is below) from the correct page, add it as a component and add it to the template and use v-bind to pass in profile from the data() part of this page. Now the imported/child component looks like this:
//ProfileText.vue
<template>
<div>
<form #submit="update">
<textarea v-model="description"></textarea>
<button type="submit">Submit</button>
</form>
<div>
<template>
<script>
export default{
name: "profileText",
props: ["profile"],
data() {
return {
description: this.$props.profile.desc
}
},
methods: {
update(e) {
e.preventDefault();
const newData = {
desc: this.$data.description
}
this.$emit('updateData', newData);
}
}
}
</script>
<style scoped>
</style>
I use v-model to bind the contents of "description" in data() to the contents of the textarea. I have it so when i edit the text area and click submit the function emits the data to the parent component which triggers a function that updates the parent data() with the new data from the text area of this component. This parts works perfectly.
However, the part I can't figure out is when the parent component executes the fetch and binds the response with the child component, why isn't the response showing up in the textarea when it loads.
I have done the exact same thing with another lot of components and it works fine on that lot. The only difference there is that with that lot the execute function brings back a response with an array of data and I use v-for(x in xs) and then bind the attributes of data() with the component x. That's the only difference. What am I missing in the code above to load the data sent in "profile" from the parent component with v-bind to the textarea in the child component with v-model. In data() i have it to return description: this.$props.profile.desc, but it is not initialising description with profile.desc - Going nuts here $#! I've been staring at the code for two days straight trying different things.
mounted Function
Called after the instance has been mounted, where el is replaced by
the newly created vm.$el. If the root instance is mounted to an
in-document element, vm.$el will also be in-document when mounted is
called.
Note that mounted does not guarantee that all child components have
also been mounted. If you want to wait until the entire view has been
rendered, you can use vm.$nextTick inside of mounted:
mounted: function () { console.log('component mounted'); }
This hook is not called during server-side rendering.
Source
Component Lifecycle
Few things:
Your syntax has errors in the ProfileText.vue file. Missing closing template and div tags
<template>
<div>
<form #submit="update">
<textarea v-model="description"></textarea>
<button type="submit">Submit</button>
</form>
</div>
</template>
You are mixing async/await and .then(). It should be:
async fetchProfile() {
const uri = 'http://localhost:8000/api/......get';
const response = await axios.get(uri);
this.updateProfileData(response.data)
},

Unknown custom element: - did you register the component correctly?

I'm new to vue.js so I know this is a repeated issue but cannot sort this out.
the project works but I cannot add a new component. Nutrition component works, profile does not
My main.js
import Nutrition from './components/nutrition/Nutrition.vue'
import Profile from './components/profile/Profile.vue'
var Vue = require('vue');
var NProgress = require('nprogress');
var _ = require('lodash');
// Plugins
Vue.use(require('vuedraggable'));
// Components
Vue.component('nutrition', Nutrition);
Vue.component('profile', Profile);
// Partials
Vue.partial('payment-fields', require('./components/forms/PaymentFields.html'));
// Filters
Vue.filter('round', function(value, places) {
return _.round(value, places);
});
Vue.filter('format', require('./filters/format.js'))
// Transitions
Vue.transition('slide', {enterClass: 'slideInDown', leaveClass: 'slideOutUp', type: 'animation'})
// Send csrf token
Vue.http.options.headers['X-CSRF-TOKEN'] = Laravel.csrfToken;
// Main Vue instance
new Vue({
el: '#app',
components: {
},
events: {
progress(progress) {
if (progress === 'start') {
NProgress.start();
} else if (progress === 'done') {
NProgress.done();
} else {
NProgress.set(progress);
}
},
'flash.success': function (message) {
this.$refs.flash.showMessage(message, 'success');
},
'flash.error': function (message) {
this.$refs.flash.showMessage(message, 'error');
}
}
});
Profile.vue
<template>
<div class="reddit-list">
<h3>Profile </h3>
<ul>
</ul>
</div>
</template>
<script type="text/babel">
export default {
name: 'profile', // this is what the Warning is talking about.
components: {
},
props: {
model: Array,
}
}
</script>
profile.blade.php
#extends('layouts.app')
#section('title', 'Profile')
#section('body-class', 'profile show')
#section('content')
<script>
window.Laravel.profileData = []
</script>
<profile></profile>
#endsection
Whenever I try to go to this page I get:
[Vue warn]: Unknown custom element: <profile> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I tried doing a local component such as
Vue.components('profile', {
template: '<div>A custom component!</div>'
});
or even I tried adding the profile into the components in vue but still no luck, can anyone point me in the right direction?
Simply clear the cache on your browser if you run into this problem. Worked pretty well for me
I didn't fixed it but it was fixed by itself it appears some kind of magic called (CACHE). i did have my gulp watch running but i powered off my computer, and then ON again and it works.

Resources