Props not updated when set from router - javascript

When I set a component props in the route definition the watcher doesn't work and the view is never updated.
In this example the update works correctly :
const Home = { template: '<div>Home</div>' }
const Test = {
template: '<div>Test : {{ nb }}<br><button #click="addOne" type="button">+1</button></div>',
props: {nb: Number},
methods: {
addOne() {
this.nb++
}
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{ name: 'test', path: '/test/:nb', component: Test, props: true }
]
})
new Vue({
router,
el: '#app'
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/">/home</router-link>
<router-link :to="{ name: 'test', params: { nb: 42 }}">/test</router-link>
<router-view></router-view>
</div>
Note : This example show a "mutating a prop directly" warning, but it's just for the simplicity it's not related to the issue since I don't do that in my code.
In this example the update doesn't work :
const Home = { template: '<div>Home</div>' }
const Test = {
template: '<div>Test : {{ counter.value }}<br><button #click="addOne" type="button">+1</button></div>',
props: {counter: Object},
methods: {
addOne() {
this.counter.value++
console.log('new value : ' + this.counter.value)
}
}
}
class Counter {
constructor(value) {
this.value = value
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{
name: 'test',
path: '/test/:nb',
props: (route) => ({counter: new Counter(route.params.nb)}),
component: Test
}
]
})
new Vue({
router,
el: '#app'
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/">/home</router-link>
<router-link :to="{ name: 'test', params: { nb: 42 }}">/test</router-link>
<router-view></router-view>
</div>

The counter object won't be reactive, so property changes won't be detected. Passing an object as a prop does not add reactivity.
I've just changed one line in your example:
props: (route) => ({counter: Vue.observable(new Counter(route.params.nb))}),
Passing the object to Vue.observable will apply the reactivity.
Note that 'applying reactivity' means rewriting all of the properties to use getters and setters. In the simple example presented here that just means the value property, which should be fine. Vue can only rewrite properties it can see, so any data hidden within closures would not be accessible.
const Home = { template: '<div>Home</div>' }
const Test = {
template: '<div>Test : {{ counter.value }}<br><button #click="addOne" type="button">+1</button></div>',
props: {counter: Object},
methods: {
addOne() {
this.counter.value++
console.log('new value : ' + this.counter.value)
}
}
}
class Counter {
constructor(value) {
this.value = value
}
}
const router = new VueRouter({
routes: [
{ path: '/', component: Home },
{
name: 'test',
path: '/test/:nb',
props: (route) => ({counter: Vue.observable(new Counter(route.params.nb))}),
component: Test
}
]
})
new Vue({
router,
el: '#app'
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/">/home</router-link>
<router-link :to="{ name: 'test', params: { nb: 42 }}">/test</router-link>
<router-view></router-view>
</div>
Part of the reason why Vue does not automatically apply reactivity to objects passed as props is that the receiving component shouldn't be modifying the object anyway. It's a violation of one-way data flow. In this example you should probably be updating the route directly rather than modifying the counter object.

Related

Cannot pass props in component from Vue router

Hi I'm trying to pass props from my vue router but it's not printing anything and when logged in mounted it's returning undefined, but its giving value when I'm trying console.log(this.$route.params.id); when I try for this.id returns undefined or rather in my User template its not outputting anything, same code is working in the online tutorial that I'm watching, please help me, is there any modification happened in a recent release
let User = {
props: ['id'],
template: `
<div>Hello # {{id}}</div>
`,
mounted() {
console.log(this.$route.params); // this is returning the value
console.log(this.id); // this is giving undefined
}
}
let App = {
template: `
<div class="wrapper">
<router-view></router-view>
</div>
`
}
let router = new VueRouter({
routes: [{
path: '/user/:id',
component: User,
props: true
}, ],
})
let app = new Vue({
el: '#app',
router: router,
components: {
'app': App
}
})
router.push('/user/1')
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router#2.0.0/dist/vue-router.js"></script>
<div id="app">
<app></app>
</div>
You are using very old version of Vue Router. Just switch to current version - 3.5.2 - and your code will work as expected....
let User = {
props: ['id'],
template: `
<div>Hello # {{id}}</div>
`,
mounted() {
console.log(this.$route.params); // this is returning the value
console.log(this.id); // this is giving undefined
}
}
let App = {
template: `
<div class="wrapper">
<router-view></router-view>
</div>
`
}
let router = new VueRouter({
routes: [{
path: '/user/:id',
component: User,
props: true
}, ],
})
let app = new Vue({
el: '#app',
router: router,
components: {
'app': App
}
})
router.push('/user/1')
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router#3.5.2/dist/vue-router.js"></script>
<div id="app">
<app></app>
</div>

Getting error while adding new routes, path is required in a route configuration

I wanted to add the dynamic routes and use the same component for all the dynamic routes. I have tried the following code to render the components, but I have got the error that says:
[vue-router] "path" is required in a route configuration.
What is the proper way of adding the dynamic routes and display the same components?
const Foo = {
template: '<div>Foo</div>'
}
const Home = {
template: '<div>Home</div>'
}
const router = new VueRouter({
mode: 'history',
routes: [{
path: '/',
component: Home
}]
})
const app = new Vue({
router,
el: "#vue-app",
methods: {
viewComponent: function(path, method) {
debugger;
let tf = `${path}/${method}`;
let newRoute = {
path: tf,
name: `${path}_${method}`,
components: {
Foo
},
}
this.$router.addRoute([newRoute])
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="vue-app">
<a v-on:click="viewComponent('api/contact','get')">ddd</a>
<router-view></router-view>
</div>
Main problem is you are passing array into addRoute
Second problem is missing / at the beginning of the path (without it, you will get a "Non-nested routes must include a leading slash character" error)
Finally use $router.push to go to the new route
const Foo = {
template: '<div>Foo</div>'
}
const Home = {
template: '<div>Home</div>'
}
const router = new VueRouter({
mode: 'history',
routes: [{
path: '/',
component: Home
}]
})
const app = new Vue({
router,
el: "#vue-app",
methods: {
viewComponent: function(path, method) {
let tf = `/${path}/${method}`;
let newRoute = {
path: tf,
name: `${path}_${method}`,
component: Foo,
}
this.$router.addRoute(newRoute)
this.$router.push({ name: newRoute.name })
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.14"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="vue-app">
<a v-on:click="viewComponent('api/contact','get')">ddd</a>
<router-view></router-view>
</div>

How to get Vuejs links to switch between components?

To simplify the demo I'm using fake/pointless API)
I have the routers
const routes = [
{ path: '/', component: Menu},
{path: '/books', component: Index,
props: route => ({ api: `https://jsonplaceholder.typicode.com/posts` })
},
{path: '/videos',component: Index,
props: route => ({ api: `https://jsonplaceholder.typicode.com/todos` })
}
]
const router = new VueRouter({ routes })
const app = new Vue({ router }).$mount('#app')
The Menu component
var Menu = Vue.component('Menu', {
template: `<ul>
<li><router-link to="/">home</router-link></li>
<li><router-link :to="{ name: 'videos'}">videos</router-link></li>
<li><router-link :to="{ name: 'books'}">books</router-link></li>
</ul>`})
This is the problem, it does not switch from videos to books.
Finally the Index component
var BookComponent = {
props: {
api: { type: String }
},
data: function () {
return {
items: null,
}
},
mounted: function(){
this.getItems()
},
methods: {
async getItems() {
fetch(this.api).then(res => res.json())
.then(res =>{
this.items = res;
this.loading = false
})
},
},
template: `
<div class="home">
<p v-for="post in items" :key="post.id">{{post.title}}</p>
</div>
`
}
var Home = Vue.component('Home', BookComponent)
var Index = {
props: {
api: {type: String,},
filterBy: {type: String},
},
template: `
<div>
<Menu />
<div class="mainApp" style="margin-top: 40px!important">
<Home :api=api />
</div>
</div>`,
component: { Menu, Home },
};
It doesn't work on jsfiddle but here's the code anyway jsfiddle
You're calling named routes but you haven't defined any name in your routes so either use the path
<li><router-link to="/videos">videos</router-link></li>
<li><router-link to="/books">books</router-link></li>
or name your routes
const routes = [
{ path: '/', component: Menu},
{path: '/books', component: Index, name: 'books',
props: route => ({ api: `https://jsonplaceholder.typicode.com/posts` })
},
{path: '/videos',component: Index, name: 'videos',
props: route => ({ api: `https://jsonplaceholder.typicode.com/todos` })
}
]
To fix navigation - replace objects in router-link attribute to with to='/videos' and to='/books'
To fix render error, add v-if to post items loop, like this:
<div class="home">
<p v-if="items" v-for="post in items" :key="post.id">{{post.title}}</p>
</div>
Here is official docs for vue-router
It seems that I just needed to watch for the route changes and trigger the fetch method again.
watch: {
// call again the method if the route changes
'$route': 'getItems'
}

Set page title with Vue Router

I have created a vue-router with a page title (h1).
Every vue-router link has a meta-title. How can I dynamically set my h1 page title?
I have tried $emit, but this is not working in my router. How can I change my page title?
const dashboard = {
template: `<div>DASHBOARD</div>`
}
const about = {
template: `<div>ABOUT</div>`
}
const routes = [
{path: '/dashboard', component: dashboard, name:'dashboard', meta: { title: 'Dashboard' }},
{path: '/about', component: about, name:'about', meta: { title: 'About' }}
]
const router = new VueRouter({
routes // short for routes: routes
})
router.beforeEach((to, from, next) => {
this.$emit('pagetitle', to.meta.title); // DOESN'T WORK!!
next();
});
new Vue({
el: '#app',
router,
data() {
return {
pagetitle: 'not set'
}
},
watch: {
'$route': function(value) {
// I can use things like value.name === 'about', but "watch" doesn't set the page title at the first page load.
}
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<router-link to="/dashboard">Dashboard</router-link>
<router-link to="/about">About</router-link>
<router-view></router-view>
<h1 v-text="pagetitle"></h1>
How can I change the page title???
</div>
In your <h1></h1> tag, just use the following
{{ $route.meta.title }}
If you would like to have correct browser history use this one:
router.afterEach((to, from) => {
Vue.nextTick( () => {
document.title = to.meta.title ? to.meta.title : 'default title';
});
})
Also if you need to change the document's title-tag, in your router.beforeEach replace this line :
this.$emit('pagetitle', to.meta.title);
with
document.title = to.meta.title;
It will work.

vue-router access component data

I am playing with an example given from vue-router documentation
HTML:
<div id="app">
<h3>{{title}}</h3>
<router-link to="/">/home</router-link>
<router-link to="/foo">/foo</router-link>
<router-view></router-view>
</div>
JavaScript:
const Foo = { template: '<div>foo</div>', data: {title: "Foo" }
const Bar = { template: '<div>bar</div>', data: {title: "Bar" }
Is it possible to access selected component data outside anchor and update {{title}} header?
Full example is here
I've two possibilities. First one is to edit your parent data directly with this.$parent.title.
And the second way is to trigger an event this.$parent.$emit('changeTitle', 'foo');.
I think the last one is better, because the control of your state is always in your parent component.
const Home = {
template: '<div>Home</div>',
data: {
title: "Home"
},
mounted() {
this.$parent.title = 'home'; // directly
}
}
const Foo = {
template: '<div>Foo</div>',
data: {
title: "Foo"
},
mounted() {
this.$parent.$emit('changeTitle', 'foo'); // emit an event
}
}
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
component: Home
},
{
path: '/foo',
component: Foo
}
]
})
new Vue({
router,
el: '#app',
data: {
title: 'Initial'
},
destroy() {
this.$off('changeTitle');
},
created() {
const _self = this;
this.$on('changeTitle', function(newTitle) {
_self.title = newTitle;
});
}
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<h3>{{title}}</h3>
<router-link to="/">/home</router-link>
<router-link to="/foo">/foo</router-link>
<router-view></router-view>
</div>

Categories