Lazy load Vue components once, retain data - javascript

i'm lazy loading some components, but these components do AJAX requests. I want the component's to retain their data if they are hidden again, as to not do multiple AJAX requests.
This is my main component which contains both lazy loaded components:
<template>
<div class="media-component">
<vehicle-image-slider :vehicle-id="vehicleId"
v-if="active === 'vehicle-image-slider'"
key="vehicle-image-slider"></vehicle-image-slider>
<vehicle-360-viewer :vehicle-id="vehicleId"
v-if="active === 'vehicle-360-viewer'"
key="vehicle-360-viewer"></vehicle-360-viewer>
Slider
360
</div>
</template>
<script>
const Vehicle360viewer = () => import('./Vehicle360Viewer.vue');
const VehicleImageSlider = () => import('./VehicleImageSlider.vue');
export default {
data: function() {
return {
active: 'vehicle-image-slider'
}
},
components: {
'vehicle-360-viewer': Vehicle360viewer,
'vehicle-image-slider': VehicleImageSlider
},
props: [ 'vehicleId' ]
}
</script>
I'd imagine v-if is the wrong thing to use on the components, however I don't want to load the component unless it is displayed.

Related

Nuxt: destroy and recreate current page's component without refresh

I have a bunch of Vue components and I'm using Nuxt as the routing layer. My global layout is pretty standard:
<template>
<nav>
<nuxt-link to="/foo">foo</nuxt-link> | <nuxt-link to="/bar">bar</nuxt-link> | etc.
</nav>
<main>
<nuxt />
</main>
</template>
In each page, I update the query string when data in the vuex store changes. If the page is loaded server-side, I parse the query string to pre-load the necessary data into the store:
<template>
<h1>foo</h1>
<!-- forms and stuff -->
</template>
<script>
export default {
data() {
return {
// static data
}
},
computed: {
fooStoreParams() {
return this.$store.state.foo.params
}
},
watch: {
fooStoreParams: async function() {
await this.$nextTick()
let query = {}
if (this.fooStoreParams.page > 1) {
query.page = this.fooStoreParams.page
}
this.$router.push({ path: this.$route.path, query: query })
}
},
async asyncData({ store, query }) {
let params = {}
let data = {
form: {
page: 1
}
}
if (query.page) {
data.form.page = query.page
params.page = query.page
}
store.dispatch('foo/updateParams', params)
await store.dispatch('foo/getStuffFromAPI')
return data
}
}
</script>
This works well, but there's a feature that I'm missing.
If I'm on already on /foo?page=2&a=1&b=2 and I click on the /foo link in the main navigation, nothing happens. This makes sense considering how Nuxt/vue-router works, but what I want to happen is for the page component to be reloaded from scratch (as if you had navigated from /bar to /foo).
The only ways I can think to do this are to either 1) do a server-side request (e.g. <b-link href="/foo">) if I'm already on /foo?whatever or 2) write a resetPage() method for each individual page.
Is there a way to just tell Nuxt to destroy and recreate the current page component?
You need to use watchQuery in order to enable client-navigation for query-params:
watchQuery: ['page', 'a', 'b']
https://nuxtjs.org/api/pages-watchquery/
If you have a component e.g
<titlebar :key="somekey" />
and the value of somekey changes the component re-renders. You could maybe work around this to achieve what you want. Read more here: https://michaelnthiessen.com/force-re-render/

Changing a button to say "Loading" with a loading animation on click

I have created a Vue button that displays "Load More" and then "Loading..." when clicked and loading more content. But, I would now like to add another component being a loading animation next to the "Loading." The button works completely fine, but I just would like to add that animation alongside the word "loading."
I have tried using Vue's ref tag, but have not had much luck in successfully using that in my method.
Loader.vue:
<template>
<div
ref="sdcVueLoader"
class="sdc-vue-Loader"
>
Loading...
</div>
</template>
<script>
export default {
name: 'Loader'
</script>
App.vue:
<Button
:disabled="clicked"
#click="loadMore"
>
{{ loadMoreText }}
</Button>
<script>
import Button from './components/Button'
import Loader from './components/Loader'
export default {
name: 'ParentApp',
components: {
Button,
Loader
},
data () {
return {
loadMoreText: 'Load More',
clicked: false
}
},
methods: {
loadMore () {
if ... {
this.page += 1
this.loadMoreText = 'Loading...' + this.$refs.sdcVueLoader
this.clicked = true
this.somerequest().then(resp => {
this.clicked = false
this.loadMoreText = 'Load More'
})
return this.loadMoreText
}
}
</script>
I am hoping for the button to continue working as it is now, but now to also have the "Loader" component displaying next to "Loading..." when the button is clicked in the app.vue loadMore method.
If you want to do anything with any form of complexity in html, it is best to move it over to your template. In your case, you have two states: It is either loading, or it is not loading. So lets create a variable loading that is either true or false.
data () {
return {
loading: false,
page: 1,
}
},
methods: {
async loadMore () {
if (this.loading) {
return;
}
this.page += 1;
this.loading = true;
const response = await this.somerequest();
this.loading = false;
// Oddly enough, we do nothing with the response
}
}
Now, in the template use a v-if with a v-else:
<button
:disabled="loading"
#click="loadMore"
>
<template v-if="loading">
<icon name="loader" />
Loading...
</template>
<template v-else>
Load more
</template>
</button>
If you want to move the logic to a different component, you have two options:
Add loading as a prop to that different component, and move the template code to that component
Use a slot and pass the html directly into your loading button. This is especially useful if you have several different configurations, and don't want to deal with increasingly complex configuration options just to accommodate them all.

Dynamic / Async Component Render

I am quite new to VueJS and have been playing around with the framework for a couple of days.
I am building a sort of dashboard with a widget based look and feel and the problem I have is that when the user adds a lot of widgets to the dashboard, problems arise on the loading of the page since the widgets make simultaneous calls to the API's to retrieve subsets of data.
To give you a better understanding of what I am doing, the concept is the below. (This is a brief idea to keep the code clean and simple).
Home.vue
<template>
<div class="Home">
<h1>Homepage</h1>
<div v-for="w in widgets">
<component :is="widget"></component>
</div>
</div>
</template>
<script>
export default {
name: 'Home',
mounted() {
for (var i = 0; i < availableWidgets; i++) {
widgets.push(availableWidgets);
}
},
};
</script>
Widget 1
<template>
<div class="Widget1">
<span>Widget 1</span>
</div>
</template>
<script>
export default {
name: 'Widget1',
mounted() {
//Get data from API and render
},
};
</script>
Widget 2
<template>
<div class="Widget2">
<span>Widget 2</span>
</div>
</template>
<script>
export default {
name: 'Widget2',
mounted() {
//Get data from API and render
},
};
</script>
As you can see, I am sort of loading the widgets and adding them dynamically depending on what the user has in his dashboard.
The problem I have is that Widget 1 and Widget 2 (in my case there are like 20-30 widgets), will be making API calls and this works fine when 1 or 2 widgets are loaded. But once the page grows a lot and there will be like 10 widgets on the page, everything starts lagging.
What would you suggest to do to make this more performant? Is it possible to allow once component to load at a time before loading the second component and so on? I was thinking of adding async calls, but that would not stop the components from being loaded at the same time?
Looking forward to your feedback and help that you could provide.
A common pattern would be to have the first render be without data, then re-render whenever your data comes in. The browser will make sure that not too many network requests run at the same time, so you should not have lag perse from that. You just perceive lag, because your component does not render until the data loads.
I would suggest using something like Axios, which uses promises and makes it easy to create asynchronous http requests while still keeping your code readable.
<template>
<div class="widget graph">
<div v-if="loading">
<span>Loading...</span>
<img src="assets/loader.svg">
</div>
<div v-else>
<!-- Do whatever you need to do whenever data loads in -->
</div>
</div>
</template>
<script>
export default {
name: 'WidgetGraph',
data () {
return {
loading: true,
error: null,
graphData: {}
}
},
created () {
this.loadData();
},
methods: {
loadData () {
return axios.get(...).then((data) => {
this.loading = false;
}).catch(() => {
this.error = 'Something went wrong.... Panic!';
this.loading = false;
});
}
}
}
</script>
<style>
</style>

Vuex how to Access state data on component when it's mounted?

I'm trying to create a Quill.js editor instance once component is loaded using mounted() hook. However, I need to set the Quill's content using Quill.setContents() on the same mounted() hook with the data I received from vuex.store.state .
My trouble here is that the component returns empty value for the state data whenever I try to access it, irrespective of being on mounted() or created() hooks. I have tried with getters and computed properties too. Nothing seems to work.
I have included my entry.js file, concatenated all the components to make things simpler for you to help me.
Vue.component('test', {
template:
`
<div>
<ul>
<li v-for="note in this.$store.state.notes">
{{ note.title }}
</li>
</ul>
{{ localnote }}
<div id="testDiv"></div>
</div>
`,
props: ['localnote'],
data() {
return {
localScopeNote: this.localnote,
}
},
created() {
this.$store.dispatch('fetchNotes')
},
mounted() {
// Dispatch action from store
var quill = new Quill('#testDiv', {
theme: 'snow'
});
// quill.setContents(JSON.parse(this.localnote.body));
},
methods: {
setLocalCurrentNote(note) {
console.log(note.title)
return this.note = note;
}
}
});
const store = new Vuex.Store({
state: {
message: "",
notes: [],
currentNote: {}
},
mutations: {
setNotes(state,data) {
state.notes = data;
// state.currentNote = state.notes[1];
},
setCurrentNote(state,note) {
state.currentNote = note;
}
},
actions: {
fetchNotes(context) {
axios.get('http://localhost/centaur/public/api/notes?notebook_id=1')
.then( function(res) {
context.commit('setNotes', res.data);
context.commit('setCurrentNote', res.data[0]);
});
}
},
getters: {
getCurrentNote(state) {
return state.currentNote;
}
}
});
const app = new Vue({
store
}).$mount('#app');
And here is the index.html file where I'm rendering the component:
<div id="app">
<h1>Test</h1>
<test :localnote="$store.state.currentNote"></test>
</div>
Btw, I have tried the props option as last resort. However, it didn't help me in anyway. Sorry if this question is too long. Thank you for taking your time to read this. Have a nice day ;)
I will recommend the following steps to debug the above code:
In the Vue dev tools, check if the states are getting set after the network call
As you are trying to fetch data asynchronously, there can be a chance that data has not arrived when created/mounted hook is called.
Add an updated hook into your component and try to log or access the state and you should be able to see it.
Please provide the results from the above debugging, then I'll be able to add more details.

Setting props for components using v-for in Vue JS

I have a PhoneCard.vue component that I'm trying to pass props to.
<template>
<div class='phone-number-card'>
<div class='number-card-header'>
<h4 class="number-card-header-text">{{ cardData.phone_number }}</h4>
<span class="number-card-subheader">
{{ cardData.username }}
</span>
</div>
</div>
</template>
<script>
export default {
props: ['userData'],
components: {
},
data() {
return {
cardData: {}
}
},
methods: {
setCardData() {
this.cardData = this.userData;
console.log(this.cardData);
}
},
watch: {
userData() {
this.setCardData();
}
}
}
The component receives a property of userData, which is then being set to the cardData property of the component.
I have another Vue.js component that I'm using as a page. On this page I'm making an AJAX call to an api to get a list of numbers and users.
import PhoneCard from './../../global/PhoneCard.vue';
export default {
components: {
'phone-card': PhoneCard
},
data() {
return {
phoneNumbers: [],
}
},
methods: {
fetchActiveNumbers() {
console.log('fetch active num');
axios.get('/api').then(res => {
this.phoneNumbers = res.data;
}).catch(err => {
console.log(err.response.data);
})
}
},
mounted() {
this.fetchActiveNumbers();
}
}
Then once I've set the response data from the ajax call equal to the phoneNumbers property.
After this comes the issue, I try to iterate through each number in the phoneNumber array and bind the value for the current number being iterated through to the Card's component, like so:
<phone-card v-for="number in phoneNumbers" :user-data="number"></phone-card>
However this leads to errors in dev tools such as property username is undefined, error rendering component, cannot read property split of undefined.
I've tried other ways to do this but they all seem to cause the same error. any ideas on how to properly bind props of a component to the current iteration object of a vue-for loop?
Try
export default {
props: ['userData'],
data() {
return {
cardData: this.userData
}
}
}
Answered my own question, after some tinkering.
instead of calling a function to set the data in the watch function, all I had to do was this to get it working.
mounted() {
this.cardData = this.userData;
}
weird, I've used the watch method to listen for changes to the props of components before and it's worked flawlessly but I guess there's something different going on here. Any insight on what's different or why it works like this would be cool!

Categories

Resources