vue 2 lifecycle - how to stop beforeDestroy? - vue.js

Can I add something to beforeDestroy to prevent destroying the component? ?
Or is there any way to prevent destroying the component ?
my case is that when I change spa page by vue-route, I use watch route first, but I found that doesn't trigger because the component just destroy..

As belmin bedak commented you can use keep-alive
when you use keep-alive two more lifecycle hooks come into action, they are activated and deactivated hooks instead of destroyed
The purpose of keep-alive is to cache and to not destroy the component
you can use include and exclude atteibutes of the keep-alive element and mention the names of the components that shoulb be included to be cached and be excluded from caching. Here is documentation
in case you want to forecefully destroy the component even if its cached you can use vm.$destroy() here
Further you can console.log in all the lifecycle hooks and check which lifecycle hook is being called

You can use vue-route navigation-guards, so if you call next(false) inside the hook, navigation will be aborted.
router.afterEach((to, from) => {
if(your condition){
next(false) //this will abort route navigation
}
})

According to this source: https://router.vuejs.org/guide/advanced/navigation-guards.html
I suggest you to do something like this with your Vue router:
const router = new VueRouter({ }); // declare your router with params
router.beforeEach((to, from, next) => {
if(yourCondition){
next(false); // prevent user from navigating somewhere
} else {
return next(); // navigate to next "page" as usual
}
});
This will prevent destroying your Vue instance on your declared condition, and it will also prevent user from navigating to another page.

Although I would consider #Vamsi Krishna "keep-alive" answer to be the proper "VueJS way" to solve this issue, I was not willing to refactor part of my code for it.
I also couldn't use the Vue router navigation guard "as-is" because in the case of beforeRouterLeave, even though using next(false) prevented the route from continuing, the component in Vue was ALREADY destroyed. Any state I had that wasn't saved would be lost, which defeats the purpose of cancelling the route change.
This wasn't what I wanted, as I needed the state of the form/settings in the component to remain (the component reloaded itself and kept the same route).
So I came up with a strategy that still used a navigation guard, but also cached any form changes/settings I had in the component in-memory, eg. I add a beforeRouteLeave hook in the component:
beforeRouteLeave (to, from, next) {
if (!this.isFormDirty() || confirm('Discard changes made?')) {
_cachedComponentData = null // delete the cached data
next()
} else {
_cachedComponentData = this.componentData // store the cached data based on component data you are setting during use of the component
next(false)
}
}
Outside the Vue component, I initialize _cachedComponentData
<script>
let _cachedComponentData = null
module.exports = {
...component code here
}
<script>
Then in the created or mounted life cycle hooks, I can set the _cachedComponentData to "continue where the user left off" in the component:
...
if (_cachedComponentData) {
this.componentData = _cachedComponentData
}
...

Related

Vue - check localStorage before initial route (for token)

I have a Vue app which does a little localStorage and server check on app load, to determine where to initially route the user.
This is in the App's main entry component, in the created() hook
My problem is that the default / route's Component visibly loads first, then the server call and everything happens which causes the user the route to their correct location
How can I delay the rendering of the initial component until my app's main component created() method completes, and then purposely navigates the user to the correct route?
I had this problem before and I firmly believe that you must have the initial files for your routes and your router configuration.
In the configuration, you could handle the permission and router before each route and with next() . In the router file, you can set your params and check them in the index.js file(router configuration)
you could also use your localStorage data in Router.beforeeach
EDIT: I just saw you used the created method... like mentioned below use beforeRouteEnter instead with the next() parameter it provides
First of all I wouldn't recommend using a delay but instead a variable that keeps track if the API call is done or not. You can achieve this using the mounted method:
data() {
return {
loaded: false,
}
}
async mounted() {
await yourAPICALL()
if (checkIfTokenIsOkay) {
return this.loaded = true;
}
// do something here when token is false
}
Now in your html only show it when loaded it true:
<div v-if="loaded">
// html
</div>
An better approuch is using the beforeRouteEnter method which allows you to not even load the page instead of not showing it: https://router.vuejs.org/guide/advanced/navigation-guards.html

Vue2 $router.replace() does not call mounted hook

I have Nuxt Vue 2 app. There is a redirect in the mounted hook to the same route. The only difference in this route is query string. It looks like
mounted() {
...
if( !isTokenOwner ) {
const result = await this.$api.campaignNewShare.copyNewShare(this.newShareToken);
localStorage.setItem(result.data.token, new Date().getTime());
this.$router.replace({'name': 'campaigns-new', 'query': {token: result.data.token}});
this.loading = false;
return;
}
}
It seems that Vue stays on the same page and only replace the url query string parameter. But I need to redirect to the new location with whole new lifecycle.
Can somebody tell me what really happened there after the replace() call? Why it does not trigger the real redirect? Thnaks.
Ok so as documentation says
One thing to note when using routes with params is that when the user
navigates from /user/foo to /user/bar, the same component instance
will be reused. Since both routes render the same component, this is
more efficient than destroying the old instance and then creating a
new one. However, this also means that the lifecycle hooks of the
component will not be called.

Why does the browser display cached Vue.js view on route/url change?

I have a homepage with <router-link> tags to views. It is a simple master/detail relationship where the Homepage is a catalogue of products and the Product detail page/view shows information on each item.
When I first launch the website and click on an item on the Homepage view (e.g. URL: http://localhost:8080/100-sql-server-2019-licence), the Product view gets loaded and the product detail loads fine.
If I then press the back button in the browser to return to the Homepage and then click on a different Product (e.g. URL: http://localhost:8080/101-oracle-12c-licence), the URL in the browser address bar changes but I get the previous product's information. Its lightning quick and no new network calls are done which means its just showing a cached page of the previous product. If I then hit the refresh button while on that page, the network call is made and the correct product information is displayed.
I did a search online but couldn't find this problem described on the search results. Could anyone point me in the right direction of how to cause a refresh/re-render of a route when the route changes?
What is happening
vue-router will cache your components by default.
So when you navigate to the second product (that probably renders the same component as the first product), the component will not be instantiated again for performance reasons.
From the vue-router documentation:
For example, for a route with dynamic params /foo/:id, when we
navigate between /foo/1 and /foo/2, the same Foo component instance
will be reused.
The easy (but dirty) fix
The easy -but hacky and not recommended - way to solve this is to give your <router-view /> a key property, e.g.:
<router-view :key="$route.fullPath" />
This will force vue-router to re-instantiate the view component every time the url changes.
However you will loose all performance benefits you would normally get from the caching.
Clean fix: properly handling route changes
The clean way to solve this problem is to react to the route-change in your component (mostly this boils down to moving ajax calls from mounted into a $route watcher), e.g.:
<script>
export default {
data() {
return {
productDetails: null,
loading: false
};
},
watch: {
'$route': {
// with immediate handler gets called on first mount aswell
immediate: true,
// handler will be called every time the route changes.
// reset your local component state and fetch the new data you need here.
async handler(route) {
this.loading = true;
this.productDetails = null;
try {
// example for fetching your product data
const res = await fetch("http://give.me.product.data/" + encodeURIComponent(route.params.id));
this.productDetails = await res.json();
} finally {
this.loading = false;
}
}
}
}
};
</script>
Alternative: Navigation Guards
Alternatively you could also use vue-routers In-Component Navigation Guards to react to route changes:
<script>
export default {
async beforeRouteUpdate (to, from, next) {
// TODO: The route has changed.
// The old route is in `from`, the new route in `to`.
this.productData = await getProductDataFromSomewhere();
// route will not change before you haven't called `next()`
next();
}
};
</script>
The downside of the navigation guards is that you can only use them directly in the component that the route renders.
So you can't use navigation guards in components deeper within the hierarchy.
The upside is that the browser will not view your site before you call next(), which gives you time to load the data necessary before your route is displayed.
Some helpful ressources
Vue Router Navigation Guards Documentation
vue-router github issue
Similar Question about vue-router component reuse on stackoverflow

Difference between beforeRouteUpdate and watching '$route' - Vue.js?

As we know, to react to params changes in the same component we use beforeRouteUpdate hook or watching $route.
watching $route:
const User = {
template: '...',
watch: {
'$route' (to, from) {
// react to route changes...
}
}
}
beforeRouteUpdate Method:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
next()
}
}
What is the difference between these two? if both are same then why vue router introduced beforeRouteUpdate?
From the documentation on beforeRouteUpdate:
called when the route that renders this component has changed, but this component is reused in the new route. For example, given a route with params /users/:id, when we navigate between /users/1 and /users/2, the same UserDetails component instance will be reused, and this hook will be called when that happens. Because the component is mounted while this happens, the navigation guard has access to this component instance.
The documentation is admittedly unclear that the hook gets called before the value of the $route object actually changes. That's the difference between this navigation hook and setting a watcher on $route, which will get called after the value of $route has changed.
Using the beforeRouteUpdate navigation guard, you can determine whether or not you want to prevent the route from changing (by not calling next()) or go to a different route entirely (by passing a different route value like next('/foo'), next({ name: 'foo' }), etc.).
Here's an example fiddle that shows when these functions get called.
As #thanksd said $route is like guard. With watching you can't prevent the route from taking action, but with beforeRouteUpdate you can do it with next function. for example you can wait untill your data is fetched then proceed to the component.
You can find more information in vue-router documentation Data Fetching.

Vue-Router won't work when a route is activated

Consider about routes below:
/form/1
/form/2
/form/3
When the address is / and I click on /form/1 VueRouter loads my component inside router-view but when I'm in /form/1 by clicking on /form/2 nothing happens!
You need to watch for param changes in the $route and perform the correct action (e.g. making an ajax call to retrieve the information for the given id), so:
watch: {
'$route' (to, from) {
// React to route change
}
}
You may also use the beforeRouteUpdate guard (see: https://router.vuejs.org/en/essentials/dynamic-matching.html)
You can also add a unique key attribute to the router-view so that vue forcefully replaces the component instead of reusing it
<router-view :key="$route.path"></router-view>