Vue router not navigating - vue.js

I'm having problems getting VueRouter to navigate.
Within my app some pages work fine, and with identical code, other pages the routing doesn't work / page doesn't update navigate.
Are there some gotchas with the router? Like you cannot call the router from within components or... ?
named route in my app
export default new VueRouter({
routes: [
{
path: '/grams/one/:cname',
component: GramsOne,
name: "gramsOne"
},
...
Then inside a component on a page:
<q-btn v-for='(rel, key) in gram.relatedItems' :key='key'
color='secondary'
#click="goGram(rel)"
>
{{rel.cn}}
</q-btn>
// later in the script:
methods: {
goGram(gram) {
let newRoute = {
name: 'gramsOne',
params: {cname: gram.cname}
}
console.log('goGram', newRoute)
this.$router.push(newRoute)
}
},
Elsewhere on the same page, simple routes work.
The URL address will get updated in the browser.
I see the right console log with route info
But the page/content will not change.
Once the URL bar has been updated, I can hit ctrl-R and the new page will get loaded. So the destination route is working fine.
From other pages in the same app I can use the same route to target new destination and loads fine.
This is also loading with the same URL and just a query param different that is causing the problem.
/app/items/foo
/app/items/bar
where bar is a type of some property /app/items/:foo
I have tried various combinations of named routes, routes using etc, and can't really see a pattern.
"vue": "~2.5.0",
"vue-resource": "^1.3.4",
"vue-router": "^2.7.0"
Thanks any hints!

The Component in this case is the same, so vue will reuse the instance.
The this.$route in the component will change but created(), beforeMounted() and mounted() hooks won't be callled.
Which is probably where you use the this.$route.params.cname
To force vue to create a new component instance you can set a unique key on the like <router-view :key="$route.fullPath">
Another options is to react to changes in the $route with a watcher:
watch: {
"$route.params.cname": {
handler(cname) {
// do stuff
},
immediate: true
}
}

Router can be called within a component just fine. In fact, you usually call router from the component.
From the code snippets provided, at one point in the code you use cn, then at other point you use cname. That might be the problem why it never quite work for you.
I create a codesandbox to recreate your scenario, and besides that minor naming mentioned above, things seem to work just fine.

Related

Is there any way to have dynamic routes/components in vuejs router?

Hi beautiful Vuejs developers out there!
I have a little problem with routing many Vue components/pages dynamically. In this scenario I am using nested routes to have a couple of routes for my layout components and hundreds of child routes for my pages and as you can imagine I'll have to type many child routes statically or manually, and then add more when I need more child routes in the future code changes but I need a solution to simplify/solve this problem with more efficient/better way like adding those routes from what user types after the layout in the url... here is my example code code:
const routes: RouteRecordRaw[] = [
{
{
path: '/student',
component: () => import('layouts/StudentLayout.vue'),
children: [
{
path: 'dashboard',
component: () => import('pages/student/Dashboard.vue'),
},
{
path: 'profile',
component: () => import('pages/student/Profile.vue'),
},
],
},
}
As you see in this code I have a layout named Student and it has two children but I'll have to type manually hundreds of child routes for this layout and other layouts is there any way to dynamically set up those routes with what users enter after the layout name like /student/dashboard or /layout/page and match it with a component name? I mean like params in Angular, can I use the param value itself inside the router to say?
{
path: ':pagename',
component: (pagename) => import('pages/student/' + pagename + '.vue'),
},
let me know if there is an efficient way to solve this problem.
Thanks in advance!
I would, personally, not use this, or advise such an approach, nor have I done it, but this idea came to me when I read your question:
My best guess would be to have a handler component which renders a component dynamically based on a route parameter (say you have /:subpage as a child to /student and the handler component is set to that route), and an exception handler around that to show a 404 page when the user types in an inexistent/unsupported route.
For example, I would dynamically import the component by the route parameter into a predefined let (e.g. let SubpageComponent; outside the try catch block), have a try catch block around the dynamic import assignment for the respective error where catch would set the variable to a 404 page. Then I would add the SubpageComponent into the data() of the component doing the rendering of the route.
Edit
I've written out come code that, maybe, makes sense.
It's based on https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
your routes definition, changed
const routes: RouteRecordRaw[] = [
{
path: '/student',
component: () => import('layouts/StudentLayout.vue'),
children: [
{
path: '/:subpage',
component: () => import('pages/student/SubpageRenderer.vue'),
props: true,
},
],
},
]
SubpageRenderer.vue
<script>
export default {
props: ['subpage'],
data() {
return {
currentSubpage: () => import(`./${subpage}.vue`)
}
}
}
</script>
<template>
<component :is="currentSubpage"></component>
</template>
Instead of using the currentSubpage import, you can also use the subpage route prop to bind :is if subpage is the name of a registered component.
Since this would get only "dashboard" from the route, you'd need some namespacing, like "student-dashboard" with the help of template literals. You could make currentSubpage into a template literal that creates the student-${subpage} name.
I'd probably recommend importing the options object of the component designated by the subpage route parameter instead of registering all the components - if you're registering them, you might as well use vue-router the usual way :)
Also, I only think this could work! It should be tested out, and perhaps casing should be kept in mind, and maybe the Layout suffix as well (subpage will probably be all lowercase, and you'll probably have the components named in PascalCase). After uppercasing the first letter, this could also obviously lead to both /student/Dashboard and /student/dashboard being valid routes

Data passing using props in router is not working

I am trying to send data from one vue component to another by using props in router. but it is not working. whenever i try to log the props it outputs undefined. code is given below
From where data is sending
Where receiving
in index.js. router setting
None of the code you've posted matches up.
Firstly, the console logging should be just console.log(this.myprops). The point of using props is that you don't need to reference the router itself, e.g. via $router.
Next problem, you're mixing path and params. That isn't allows. See https://router.vuejs.org/guide/essentials/navigation.html. params are for named routes.
I imagine what you're aiming for is something like this:
self.$router.replace({ name: 'DashboardPatient', params: { myprops: authUser.email } })
with router config:
{
path: '/patient',
component: Dash,
children: [
{
path: ':myprops', // <--- Adding myprops to the URL
name: 'DashboardPatient',
component: DashboardPatient,
props: true,
meta: { requiresAuth: true }
}
]
}
Keep in mind that routing is all about building and parsing the URL. So the value of myprops needs to be in the URL somewhere. In my example it comes at the end, so you'll get /patient/user#example.com as the URL. If it weren't in the URL then there'd be no way for the router to populate the prop if the user hit that page directly (or refreshed the page).
To hit the same route using a path instead of a name it'd be something like this:
self.$router.replace({ path: `patient/${encodeURIComponent(authUser.email)}` })
or even just:
self.$router.replace(`patient/${encodeURIComponent(authUser.email)}`)
Personally I'd go with the named route so that the encoding is handled automatically.
If you don't want to put the data in the URL then routing is not the appropriate way to pass it along. You'd need to use an alternative, such as putting it in the Vuex store.

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.js with VueRouter - how to correctly route on user's refresh

I use Vue and VueRouter (and also Vuex but it is not the case here) in my project. Imagine i have 5 files:
main.js - stores all components definitions, imports them from
external files and so on.
App.vue - it is main component that stores
all other
routes.js - stores all the routing definitions
login.vue -
stores login component (login page)
content.vue - stores page
component
(quite simplified version but you surely get the idea).
Now if i open my path '/' it should reroute me to '/login' page if i am not logged in and to '/content' when i am logged in. Nothing special here.
Now my page works as intended (almost). If I enter in my browser '/content' it tries to render '/content' component with default data (ie userId = -1), then immediately it reroutes me to '/login' page. The '/content' shows just for a second. And it is my problem. I would like to reroute to '/login' without earlier rendering '/content' with default data.
It is obvious that it tries to render '/content' - maybe from cache or something, but since rerouting is my first command in created() it should not
mount /content component in app component, but /login.
Any idea how to prevent it?
I am aware that i do not attach any code, but i hope it wont be necessery for you to understand the problem and advice any solution because it would need cutting and simpliding a lot of code.
In your case, I think you should use vue router's beforeEach hook.
You can use meta field in router to indicates whether the path need authentication, and do processing in beforeEach function.
I will give the sample code.
import Router from 'vue-router';
const router = new Router({
routes: [{
path: '/content',
meta: {
auth: true,
}
}, {
path: '/login',
}]
});
router.beforeEach(async (to, from, next) => {
if (to.matched.some(m => m.meta.auth)) {
// user your authentication function
const isAuth = await getAuthentication;
if (!isAuth) {
next('/login');
}
next();
}
})
if your authentication function is not async function, you should remove async/await keywords
Except if the API in the meantime declares that you are no longer authenticated, the router will not be able to refresh itself by the beforeEach method.
Even with a loop method that retrieves data from the API, which will store them in the store as reactive data.
In Vue everything can be reactive, except Vue router

Vue-multiselect inconsistent reactive options

So I'm building an application using Laravel Spark, and therefore taking the opportunity to learn some Vue.js while I'm at it.
It's taken longer for me to get my head around it than I would have liked but I have nearly got Vue-multiselect working for a group of options, the selected options of which are retrieved via a get request and then updated.
The way in which I've got this far may well be far from the best, so bear with me, but it only seems to load the selected options ~60% of the time. To be clear - there are never any warnings/errors logged in the console, and if I check the network tab the requests to get the Tutor's instruments are always successfully returning the same result...
I've declared a global array ready:
var vm = new Vue({
data: {
tutorinstruments: []
}
});
My main component then makes the request and updates the variable:
getTutor() {
this.$http.get('/get/tutor')
.then(response => {
this.tutor = response.data;
this.updateTutor();
});
},
updateTutor() {
this.updateTutorProfileForm.profile = this.tutor.profile;
vm.tutorinstruments = this.tutor.instruments;
},
My custom multiselect from Vue-multiselect then fetches all available instruments and updates the available instruments, and those that are selected:
getInstruments() {
this.$http.get('/get/instruments')
.then(response => {
this.instruments = response.data;
this.updateInstruments();
});
},
updateInstruments() {
this.options = this.instruments;
this.selected = vm.tutorinstruments;
},
The available options are always there.
Here's a YouTube link to how it looks if you refresh the page over and over
I'm open to any suggestions and welcome some help please!
Your global array var vm = new Vue({...}) is a separate Vue instance, which lives outside your main Vue instance that handles the user interface.
This is the reason you are using both this and vm in your components. In your methods, this points to the Vue instance that handles the user interface, while vm points to your global array that you initialized outside the Vue instance.
Please check this guide page once more: https://v2.vuejs.org/v2/guide/instance.html
If you look at the lifecycle diagram that initializes all the Vue features, you will notice that it mentions Vue instance in a lot of places. These features (reactivity, data binding, etc.) are designed to operate within a Vue instance, and not across multiple instances. It may work once in a while when the timing is right, but not guaranteed to work.
To resolve this issue, you can redesign your app to have a single Vue instance to handle the user interface and also data.
Ideally I would expect your tutorinstruments to be loaded in a code that initializes your app (using mounted hook in the root component), and get stored in a Vuex state. Once you have the data in your Vuex state, it can be accessed by all the components.
Vuex ref: https://vuex.vuejs.org/en/intro.html
Hope it helps! I understand I haven't given you a direct solution to your question. Maybe we can wait for a more direct answer if you are not able to restructure your app into a single Vue instance.
What Mani wrote is 100% correct, the reason I'm going to chime in is because I just got done building a very large scale project with PHP and Vue and I feel like I'm in a good position to give you some advice / things I learned in the process of building out a PHP (server side) website but adding in Vue (client side) to the mix for the front end templating.
This may be a bit larger than the scope of your multiselect question, but I'll give you a solid start on that as well.
First you need to decide which one of them is going to be doing the routing (when users come to a page who is handling the traffic) in your web app because that will determine the way you want to go about using Vue. Let's say for the sake of discussion you decide to authenticate (if you have logins) with PHP but your going to handle the routing with Vue on the front end. In this instance your going to want to for sure have one main Vue instance and more or less set up something similar to this example from Vue Router pretending that the HTML file is your PHP index.php in the web root, this should end up being the only .php file you need as far as templating goes and I had it handle all of the header meta and footer copyright stuff, in the body you basically just want one div with the ID app.
Then you just use the vue router and the routes to load in your vue components (one for each page or category of page works easily) for all your pages. Bonus points if you look up and figure using a dynamic component in your main app.vue to lazy load in the page component based on the route so your bundle stays small.
*hint you also need a polyfill with babel to do this
template
<Component :is="dynamicComponent"/>
script
components: {
Account: () => import('./Account/Account.vue'),
FourOhFour: () => import('../FourOhFour.vue')
},
computed: {
dynamicComponent() {
return this.$route.name;
}
},
Now that we are here we can deal with your multiselect issue (this also basically will help you to understand an easy way to load any component for Vue you find online into your site). In one of your page components you load when someone visits a route lets say /tutor (also I went and passed my authentication information from PHP into my routes by localizing it then using props, meta fields, and router guards, its all in that documention so I'll leave that to you if you want to explore) on tutor.vue we will call that your page component is where you want to call in multiselect. Also at this point we are still connected to our main Vue instance so if you want to reference it or your router from tutor.vue you can just use the Vue API for almost anything subbing out Vue or vm for this. But the neat thing is in your main JS file / modules you add to it outside Vue you can still use the API to reference your main Vue instance with Vue after you have loaded the main instance and do whatever you want just like you were inside a component more or less.
This is the way I would handle adding in external components from this point, wrapping them in another component you control and making them a child of your page component. Here is a very simple example with multiselect pretend the parent is tutor.vue.
Also I have a global event bus running, thought you might like the idea
https://alligator.io/vuejs/global-event-bus/
tutor.vue
<template>
<div
id="user-profile"
class="account-content container m-top m-bottom"
>
<select-input
:saved-value="musicPreviouslySelected"
:options="musicTypeOptions"
:placeholder="'Choose an your music thing...'"
#selected="musicThingChanged($event)"
/>
</div>
</template>
<script>
import SelectInput from './SelectInput';
import EventBus from './lib/eventBus';
export default {
components: {
SelectInput
},
data() {
return {
profileLoading: true,
isFullPage: false,
isModalActive: false,
slackId: null,
isActive: false,
isAdmin: false,
rep: {
id: null,
status: '',
started: '',
email: '',
first_name: '',
},
musicTypeOptions: []
};
},
created() {
if (org.admin) {
this.isAdmin = true;
}
this.rep.id = parseInt(this.$route.params.id);
this.fetchData();
},
mounted() {
EventBus.$on('profile-changed', () => {
// Do something because something happened somewhere else client side.
});
},
methods: {
fetchData() {
// use axios or whatever to fetch some data from the server and PHP to
// load into the page component so say we are getting the musicTypeOptions
// which will be in our selectbox.
},
musicThingChanged(event) {
// We have our new selection "event" from multiselect so do something
}
}
};
</script>
this is our child Multiselect wrapper SelectInput.vue
<template>
<multiselect
v-model="value"
:options="options"
:placeholder="placeholder"
label="label"
track-by="value"
#input="inputChanged" />
</template>
<script>
import Multiselect from 'vue-multiselect';
export default {
components: { Multiselect },
props: {
options: {
type: [Array],
default() {
return [];
}
},
savedValue: {
type: [Array],
default() {
return [];
}
},
placeholder: {
type: [String],
default: 'Select Option...'
}
},
data() {
return {
value: null
};
},
mounted() {
this.value = this.savedValue;
},
methods: {
inputChanged(selected) {
this.$emit('selected', selected.value);
}
}
};
</script>
<style scoped>
#import '../../../../../node_modules/vue-multiselect/dist/vue-multiselect.min.css';
</style>
Now you can insure you are manging the lifecycle of your page and what data you have when, you can wait until you get musicTypeOptions before it will be passed to SelectInput component which will in turn set up Multiselect or any other component and then handle passing the data back via this.$emit('hihiwhatever') which gets picked up by #hihiwhatever on the component in the template which calls back to a function and now you are on your way to do whatever with the new selection and pass different data to SelectInput and MultiSelect will stay in sync always.
Now for my last advice, from experience. Resist the temptation because you read about it 650 times a day and it seems like the right thing to do and use Vuex in a setup like this. You have PHP and a database already, use it just like Vuex would be used if you were making is in Node.js, which you are not you have a perfectly awesome PHP server side storage, trying to manage data in Vuex on the front end, while also having data managed by PHP and database server side is going to end in disaster as soon as you start having multiple users logged in messing with the Vuex data, which came from PHP server side you will not be able to keep a single point of truth. If you don't have a server side DB yes Vuex it up, but save yourself a headache and wait to try it until you are using Node.js 100%.
If you want to manage some data client side longer than the lifecycle of a page view use something like https://github.com/gruns/ImmortalDB it has served me very well.
Sorry this turned into a blog post haha, but I hope it helps someone save themselves a few weeks.