How to destroy VueJS component in a method? - vue.js

I am having a VueJS application with a Tabs component, where user can open different tabs. Also I’m supporting user accounts, so each user can have his own tabs.
But here is the catch: if I’m logged with one user, then I’m logging out and right after that I’m logging in with different user. For a second (or two) after the second user is logged, I’m then able to see the tabs which the previous user has and immediately they are overwritten with the tabs for the second user.
So how I’m able to prevent this to happen? I assume this can be done on in a method when the “log out” button is clicked.
To be more precise, what I’m having is a router and two pages (LoginPage and MainPage) and with logging out I’m redirected with the router to the LoginPage.
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'MainPage',
component: MainPage
},
{
path: '/login',
name: 'LoginPage',
component: LoginPage,
}
]
}
Data in the MainPage comes on it’s create() and mounted() events and I assume that by forcing components to be recreated again will resolve my issue. Since this is not something that I want to save from user to user, instead it should have the effect of loading as when I’m visiting for the first time.

Try assigning a unique key to your MainPage component that's tied to your user's id to force it to re-render when the user changes. Something like ..
<main-page :key="user.id" ...>

Related

Dynamically load component as per user role Vue Router

I'm trying to load specific components as per the user role in my Vue router.js.
Roles are handled through websanova vue auth and the easiest way to check if a user has a role is by doing Vue.auth.check('rolename')
In my route file, I've tried with something like this:
{
name: 'profile',
path: '/profile',
component: () => Vue.auth.check('secretary')
? import('#/components/secretaries/LayoutProfile')
: import('#/components/doctors/LayoutProfile'),
props: (route) => ({
editMode: false
}),
meta: {
auth: true
}
},
It seems to work for the first time: once the secretary logs in the secretaries/LayoutProfile component is mounted as expected.
But the issue comes if I log out as a secretary and log in as a doctor: the secretaries/LayoutProfile is still being used instead of doctors/LayoutProfile. The only way to fix it is by refreshing the page.
I'm not pretty sure how to deal with this, so far I can just imagine that router.js is loaded on my App mount lifecycle hook, therefore and despite log-in/log-out the router keeps the secretary component mounted.
I just want to be sure that this is not possible before thinking of another approach such as dynamically loading children components within a parent Profile component as per user roles.
FYI, I'm facing no errors at all, just wrong components mounted

How do I detect the page the user came from in vue.js when the page opens in a new tab?

I've got an interesting problem in my vue.js application and I don't know how to solve it.
We've got a "my listings" page that shows a grid of listings that the user created. When they click on one, it takes them to the listing details page. It opens this page in a new browser tab.
What we want to do is add a new component to the top of the page that shows the user the stats on their listing. But we want this component to show up ONLY when they come to the listing details page from the My Listings page. There are other ways of getting to the Listing Details page and we don't want the stats component to show up when they come from these other ways.
I would think this could be handled in the router. I tried seeing if I could detect that the user was coming from the My Listings page from the "from" parameter in the beforeEach(...) method of the router. I did this:
router.beforeEach(async (to, from, next) => {
console.log('from=', from);
console.log('to=', to);
});
When it prints the from parameter, I get this:
to= {
fullPath: "/"
hash: ""
matched: []
meta: {}
name: null
params: {}
path: "/"
query: {}
}
It contains no information about where it came from. I'm guessing this is because it opens the Listing Details page in a new tab. So I can't use the router to tell where the user came from.
Instead, I resorted to using localStorage:
On the My Listings page:
<v-btn :href="`/listings/${listing.listingId}`" target="_blank" #click="saveFromMyListings();">View Listing</v-btn>
...
saveFromMyListings() {
localStorage.setItem('from-my-listings', true);
},
On the Listing Details page:
async created () {
this.fromMyListings = localStorage.getItem('from-my-listings') === 'true';
localStorage.setItem('from-my-listings', false);
},
So long as I set the 'from-my-listings' item in localStorage to false immediately after I use it to determine that the user came from the My Listings page, it works. That way, it is ONLY set if the user comes from the My Listings page, and never set if the user comes from anywhere else.
The problem with this method is that if the user refreshes the page, the stats disappear. Obviously, this is because created() reruns and this time 'from-my-listings' is removed from localStorage. I can fix this by not setting it to false in created() once it's used, but then where do I remove it in such a way that it's guaranteed to be removed no matter how the user leaves the page (entering a new url directly in the browser, closing the browser, computer loses power, etc.)?
Is there some other hook in vue.js besides created() that runs only once (when the user first visits the page) but not on subsequent loads (like refresh)? Is there a way to pass props to a component in the router based on the state of localStorage that won't have to be passed again on refresh? What other solutions might there be to this problem?
You could use query parameters. You'd have to change the links to something like this:
yourapp.com/listing-detail/333?from=list
then in the created function you can check window.location.search for the from value

why parent component never render until child router is complete?

In my vue application I have route inside route. the problem is until the inner route is resolved then the outer/parent route is display.
Here is how I defined the routes:
const router = new VueRouter({
// Use the HTML5 History API (fallback to URL hash if unsupported)
mode: "history",
routes: [
{
path: "/",
name: "base",
component: Base,
children: [{ path: "", name: "home", component: Home }]
}
]
});
The problem is vue is waiting for beforeRouteEnter to complete then it show the Home and Base.
If I remove the next() from beforeRouterEnter in the Home the component base is never display.
This is a problem because Home can take a lot of time to load data meanwhile base should be render to screen (base has toolbar for example).
Here is example of the problem
How can I solve this issue?
Sometimes you need to fetch data from the server when a route is
activated. We can achieve this in two different ways:
Fetching After Navigation: perform the navigation first, and fetch
data in the incoming component's lifecycle hook. Display a loading
state while data is being fetched.
Fetching Before Navigation: Fetch data before navigation in the
route enter guard, and perform the navigation after data has been
fetched.
Since your fetch takes too much time, your preferable approach would be the first aforementioned. Jest perform your API calls in mounted/created hooks of Home.vue component.

Vue router reloading the current route

Without reloading the whole page I need to reload the current route again (Only a component reload) in a vue app.
I am having a path in vue router like below,
{
path: "/dashboard",
name: "dashboard",
component: loadView("Dashboard"),
},
When user clicks on the Dashboard navigation item user will be redirected to the Dashboard page with vue router programmatic navigation
this.$router.push({ name: "dashboard" });
But when user already in the dashboard route and user clicks the Dashboard nav item again nothing happens. I think this is vue router's default behaviour. But I need to force reload the Dashboard component (Not to refresh the whole page).
I can't use beforeRouteUpdate since the router is not updated. Also I have tried the global before guards like beforeEach. But it is also not working.
How can I force reload the dashboard component without reloading the whole page?
It can be done in two ways.
1) Try doing vm.$forceUpdate(); as suggested here.
2) You can take the strategy of assigning keys to children, but whenever you want to re-render a component, you just update the key.
<template>
<component-to-re-render :key="componentKey" />
</template>
<script>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
</script>
Every time that forceRerender is called, the prop componentKey will change. When this happens, Vue will know that it has to destroy the component and create a new one.
What you get is a child component that will re-initialize itself and “reset” its state.
Not mentioned here, but as the offered solutions require a lot of additional work just to get the app to render correctly, which imo is a brittle solution.. we have just implemented another solution which works quite well..
Although it is a total hack.
if (this.$route.name === redirect.name) {
// this is a filthy hack - the vue router will not reload the current page and then have vue update the view.
// This hack routes to a generic page, then after this has happened the real redirect can happen
// It happens on most devices too fast to be noticed by the human eye, and in addition does not do a window
// redirect which breaks the mobile apps.
await this.$router.push({
name: RouteNames.ROUTE_REDIRECT_PLACEHOLDER
});
}
... now continue to do your normal redirect.
Essentially, redirect to a placeholder, await the response but then immediately continue to another page you actually wanted to move toward

Vue router view event propagation

I cant't figure out why the router-view does not emit the "login" event.
Here's the fiddle I'm playing with: https://jsfiddle.net/cvtwxf6h/22/
I want 2 different layouts, one for logged user and another for not logged user. The layout to display is determined by the logged property of the Index component.
When I click "Login" in the login page, a "login" event should propagate up to the Index component to update the logged property and change layout. For some reason the router-view does not emit the event, what am I doing wrong?
(I just want to understand the problem, I'm not interested in alternative ways to achieve this)
The problem seems to be the router-link navigates to a different route (via to="{name: 'index'}") before the login event is emitted, which causes the event to be lost somehow. If the target route is the same as the current route (no navigation), the event reaches the parent component without a problem.
A workaround would be to imperatively navigate with $router.push() after emitting the event:
const LoginPage = {
template: `<router-link to="" #click.native="switchToLoggedPage({ name: 'index' })">Login</router-link>`,
methods: {
switchToLoggedPage(route) {
this.$emit('login');
this.$router.push(route);
},
},
};
demo