Vue Routing - prevent re-rendering the page? - vue.js

I have News.vue and News-View.vue. When I route to news/1, it opens the News-View.vue page. The problem is that I have many search filters (category, date, etc.) and infinite scroll in News.vue. This means, that when the user gets back from News-View.vue, everything re-renders and refreshes, and the user's preferences are cleared. Is there a way to not re-render the News.vue page?
News.Vue:
beforeMount: async function () {
this.$axios.post('http://localhost/?action=news', this.filters).then((response) => {
this.results = this.results.concat(response)
})
}
P.S. I'm currently using State Management for saving the loaded results and saved scroll position and it works, but again, I was wondering is there a way to just not re-render a page, but save the current state or something...

You could try to use the keep-alive tag:
<keep-alive>
<router-view></router-view>
</keep-alive>
https://v2.vuejs.org/v2/guide/components-dynamic-async.html
https://router.vuejs.org/api/
However, you'll need to tweak it a little bit because I'm not sure you would want that behavior for your entire app?

Related

Scrolling to v-expansion-panel opening

I'm trying to build a mobile small application using v-expansion-panels to display a list.
The idea is that when the user adds a new item in such list it will open the new panel and scroll down to such new panel.
I found a goTo() method in the $vuetify variable, unfortunatly the v-expansion-panels transition (the "opening") take some time and the goTo() won't completely scroll down because of the scrollbar height changes.
So from my understanding I need to detect the end of the transition (enter/afterEnter hook).
Per the vuetifyjs documentation, I could hope to have a "transition" property on my component. (Which isn't the case anyway). But such property is only a string so I can't hook into it.
My other idea is to, somehow, find the transition component and hook into it. Unfortunatly I have trouble understanding el/vnode and the way vuejs is building is tree like the vue-devtool show and I can't get the transition component. When debugging (in the enter hook callback of the transition) it is like the component/el/vnode has a parent but isn't the child of anybody.
Is there a way to do what I'm looking for?
Here is a jsfiddler of what I currently do: https://jsfiddle.net/kdgn80sb/
Basically it is the method I'm defining in the Vue:
methods: {
newAlarm: function() {
const newAlarmPanelIndex = this.alarms.length - 1;
this.alarms.push({title: "New line"});
this.activePanelIndex = newAlarmPanelIndex;
// TODO:
this.$vuetify.goTo(this.$refs.alarmPanels[newAlarmPanelIndex]);
}
}
Firstly you should open manually panel and then use goTo with a timeout.
It works for me, just give some time to a panel to open.
<v-expansion-panels v-model="alarmPanels">
<v-expansion-panel>
<div id="example" />
</v-expansion-panel>
</v-expansion-panels>
this.alarmPanels.push(0); // Use index of expansion-panel
setTimeout(() => {
this.$vuetify.goTo(`#${resultId}`);
}, 500);

Lazy loading data from API in Vue.js

I want to lazy load content when the user scrolls down to the bottom of the Bootstrap modal, sort of like an infinite scroll, using Vue.js. I'm fetching all my data from a action method on API call. The data coming from this API is stored in array of objects on mounted and is used in the application.
So far so good. But now I want to implement that in the initial state, only the first 10 items are fetched from the API. When the user scrolls down to the bottom of the page I want to fetch the next 10 results and so on. I've looked at the documentation of the API that I'm using, and it has support for offsetting the items I'm fetching. Though I'm not sure where to start from there. Does anyone know any good resources on this subject? Thanks a ton!
after a while i solved the problem
here is my sample project for you reading this question
Here is my solution if you are trying to avoid using an npm package. You can use it in any scrollable div in vue. And in the if statement below is where you would handle your api call to fetch more results.
<template>
<div class="scroll" #scroll="scroll">
</div>
</template>
<script>
export default{
name: "Scroll",
data(){
return{
bottom: false
}
},
methods:{
scroll(e){
const {target} = e;
if (Math.ceil(target.scrollTop) >=
target.scrollHeight - target.offsetHeight) {
//this code will run when the user scrolls to the bottom of this div so
//you could do an api call here to implement lazy loading
this.bottom = true;
}
}
}
</script>

v-navigation-drawer drops into a runaway loop on window resize

First, let me say that the v-navigation-drawer works as intended, i.e.:
On clicking the hamburger menu the TOGGLE_DRAWER mutation is committed, and it toggles open/closed, updating the state.
On window resize it opens/closes at a designated breakpoint
So it works.
BUT the window resize does not properly toggle the mutation and I keep getting a Vuex mutation error when I resize the window:
I understand why I'm getting this error - the $store.state.ui.drawer is being modified outside of the mutator (it's the v-navigation-drawer's v-model):
<v-navigation-drawer
v-model="$store.state.ui.drawer"
app
clipped
>
I get it's bad form to bind the state to the v-model. But when I try to make a drawer computed property with a get() and set() method that properly gets/commits a mutation, the browser crashes (presumably because the set method triggers an endless loop of commits toggling drawer true/false into infinity):
computed: {
drawer: {
get () {
return this.$store.state.ui.drawer
},
set () {
this.$store.commit('TOGGLE_DRAWER') // <--crashes the browser
}
}
}
I've searched endlessly for a solution to this problem. It's bugging me even though it visually appears to be working.
I've considered running the v-navigation-drawer in stateless mode and handling all the window resize events and state updates manually. I've also considered disabling 'Strict' mode in Vuex (which would hide the errors). But the former is a lot more complexity and the latter is a bandaid that costs me debugging insight in development.
This sounds like a perfect candidate for Lodash's debounce function. If you need to stick with using setter/getter while applying this effect, have a look at this post; otherwise, this one for sequential event subscription on any of the lifecycle hooks.
After spending some time with this, I think I have a solution. Wanted to share for anyone else that may be facing the same issue with VNavigationDrawer using Vuex state to control visibility.
The #input event passes a val parameter, which includes the state of the drawer after the window resizes. I created a new action that is called by the below function:
<v-navigation-drawer
:value="$store.state.ui.drawer"
app
clipped
#input="updateDrawer($event)"
>
Here is the action being dispatched:
methods: {
updateDrawer(event) {
if (event !== this.drawer) { // avoids dispatching duplicate actions; checks for unique window resize event
this.$store.dispatch('updateDrawer',event)
}
}
},
And the action commits the new val to the Vuex store.
Basically, the input event is able to watch for updates to the drawer, and subsequently update the drawer state if it's necessary.
You'll also see above that I stubbornly accepted using :value as the docs suggest, even though I think this should be controlled by a v-model.
Seems to be working - with the right events called and the state being updated appropriately.

vuejs - Keep Form data when browser back

I do not speak English, I'm using google translator.
I am having a problem with the back button after a form submit, I use vuejs to display content and manipulate / validate a form, clicking the submit button is redirected to another action. if I click back in the browser vuejs reloads the previous page, so I lose the data of my form, how do I keep the data of a form without using a , since it is a submit button? I believe that the tag does not suit me in this case (I tested it and it did not work).
Simply you can use keep-alive in Vue js to cache states on back action.
For more information:
https://v2.vuejs.org/v2/api/#keep-alive
keep alive support in Vue2 and also Vue3, but the syntax is different. Also you can use in Nuxt.
For better performance you should limit components to be caches by "max" and "include" props.
you can use in layout or only wrap components:
Wrap in layout:
<keep-alive>
<router-view :key="$route.fullPath"></router-view>
</keep-alive>
Wrap components to cache:
<keep-alive>
<product-list></product-list>
</keep-alive>
Nuxt:
<template>
<div>
<Nuxt keep-alive :keep-alive-props="{ exclude: ['modal'],max:3 }" />
</div>
</template>
** Keep alive should be in the same layout(if you use keep alive in layout).
** If you destroy keep alive cache, the caching will not work anymore.
** You can control cache with "key" props. For same "key" component will show from cache.
** Keep alive only cache states. So for keep scroll, you should store last position as state and set scroll position on activated hook.
** Lifecycle doesn't work on cache mode and you should use activated and deactivated hooks.
The HTML5 History API seems made for this.
When formdata is changed save it in the history with:
window.history.replaceState(state, "");
And when (re)loading the page load from history with
window.history.state
Or look into window.addEventListener("popstate", function(){...})
E.g. in my listview I use:
const methods = {
saveStateToHistory() {
var state = window.history.state || {};
if( ! state[this.id]) {
state[this.id] = {};
}
state[this.id].currentPage = this.currentPage;
state[this.id].currentPerPage = this.currentPerPage;
state[this.id].collFilter = this.collFilter;
window.history.replaceState(state, "");
},
};
const watch = {
currentPage(newdata) {
this.saveStateToHistory();
},
currentPerPage(newdata) {
this.saveStateToHistory();
},
collFilter(newdata) {
this.saveStateToHistory();
},
};
function created() {
var state = window.history.state || {};
var myState = state[this.id];
if(myState) {
this.currentPage = myState.currentPage;
this.currentPerPage = myState.currentPerPage;
this.collFilter = myState.collFilter;
}
};
This keeps the current page and selected filters after someone folows a link from the list-view and then uses the browser back-button to go back to the list-view.
I'm using state[this.id] to allow for history state from different components in the same page.

vuejs where in the code/lifecycle should data retrieval be triggered

I am working on a vuejs SPA.
I have a view that shows a list of items and another view that shows details for a specific Item.
when I click the item I switch views using:
this.$router.push('/item/' + event.ItemId );
The data is managed using vuex modules.
I would like to allow some temporary display while the item details are being retried (i.e. not to block the rendering of the item details view which should know on its own to indicate that it is still awaiting data).
And I would also have to consider that it should work if the URL is changed (I think I read that there is an issue with the view not being reloaded/recreated when only the item id would change in the URL.
Where would be the appropriate place (code/lifecycle) to trigger the (async) retrieval of the data required for rendering the item details view?
I would like to allow some temporary display while the item details are being retried (i.e. not to block the rendering of the item details view which should know on its own to indicate that it is still awaiting data).
One way to achieve this, is to define a state variable, named e.g. isLoading, in the data context of the Vue component. This variable would then be true while the data is retrieved asynchronously. In the template, you can use v-if to display a spinner while loading, and displaying the content after that.
If you are retrieving the data multiple times (refreshing the view), I would move the retrieving code into a method, e.g. called loadData. In the mounted section of the Vue component you then can just initially call this method once.
Here is some example code:
<template>
<div>
<button #click="loadData" :disabled="isLoading">Refresh</button>
<div class="item" v-if="!isLoading">
{{ item }}
</div>
<div class="spinner" v-else>
Loading...
</div>
</div>
</template>
<script>
import HttpService from '#/services/HttpService';
export default {
name: 'item-details',
data () {
return {
isLoading: false,
item: {}
};
},
methods: {
loadData () {
this.isLoading = true;
HttpService.loadData().then(response => {
this.item = response.data;
this.isLoading = false;
}, () => {
this.item = {};
this.isLoading = false;
});
}
},
mounted () {
this.loadData();
}
};
</script>
And I would also have to consider that it should work if the URL is changed (I think I read that there is an issue with the view not being reloaded/recreated when only the item id would change in the URL.
This issue you mentioned occurs if you are not using the HTML5 history mode, but an anchor (#) in the URL instead. If you are just changing the part after the anchor in the URL, the page is not actually refreshed by the browser. The Vue component won't be reloaded in this case and the state is still old. There are basically two ways around this:
You are switching from anchors in the URL to a real URL with the HTML5 history mode, supported by the Vue Router. This requires some back-end configuration, though. The browser then does not have this faulty behavior, because there is no anchor. It will reload the page on every manual URL change.
You can watch the $route object to get notified on every route change. Depending on if the user is changing the part after the anchor, or before, the behavior is different (it also depends where the cursor is, when you hit enter). If the part after the anchor is changed (your actual Vue route), only the component is notified. Otherwise, a full page refresh is made. Here's some example code:
// ...inside a Vue component
watch: {
'$route' (to, from) {
this.loadData();
}
}