I'm using async components in my code on CodeSandbox (seen below). When I click on goto product-2 after goto product-1, nothing happens. I expected the <product-item> component to change based on the clicked component, but only the URL changes. I even have a beforeRouteUpdate hook function. How do I fix this?
// main.js
import Vue from "vue";
import App from "./App.vue";
import VueRouter from 'vue-router';
import ProductPage from './product-page.vue';
Vue.use(VueRouter);
Vue.config.productionTip = false;
const routes = [
{ path: '/:productId', component: ProductPage },
]
const router = new VueRouter({
routes // short for `routes: routes`
})
new Vue({
router,
render: h => h(App)
}).$mount("#app");
// App.vue
<template>
<div id="app">
<router-link to="/product-1">goto product1</router-link>
<br>
<br>
<router-link to="/product-2">goto product2</router-link>
<div>Router view:</div>
<router-view :key="$route.params.productId"></router-view>
</div>
</template>
// product-page.vue
<template>
<div>
<product-item></product-item>
</div>
</template>
<script>
export default {
name: "product-page",
components: {
ProductItem: () => ({
component: import("./product-item.vue"),
loading: { template: "<div>loading....</div>" }
})
}
};
</script>
<template>
<div>
product item: {{product}}
</div>
</template>
<script>
export default {
name: "ProductItem",
mounted: function () {
this.product = this.$route.params.productId;
},
beforeRouteUpdate: function(to, from, next) {
this.product = to.params.productId;
next();
},
data: function () {
return {
product: null
}
},
};
</script>
The problem is the route path (i.e., /:productId) does not actually change between the links for /product-1 and /product-2 (even though the parameter values do), so router-view does not re-render.
The workaround is to key the router-view based on the productId parameter:
<router-view :key="$route.params.productId" />
Related
When I want to practice a navigation bar with Vue, encountering a mistake about the invalid route component. In this div cannot display anything. As to the hint of the console, I can find no way out.
the App.vue, hereI only show the Home navigation
<template>
<div id="app">
<tab-bar>
<tab-bar-item class="tab-bar-item" path='/home' activeColor="red">
<img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="">
<img slot="item-icon-active" src="./assets/img/tabbar/Home1.svg" alt="">
<div slot="item-text">Home</div>
</tab-bar>
<router-view></router-view>
</div>
</template>
<script>
import TabBar from './components/tabbar/TabBar'
import TabBarItem from './components/tabbar/TabBarItem'
export default {
name: 'App',
components: {
TabBar,
TabBarItem
}
}
</script>
the two vue components:
TabBarItem.vue
<template>
<div class="tab-bar-item" #click="itemClick">
<div v-if="!isActive"><slot name="item-icon"></slot></div>
<div v-else><slot name="item-icon-active"></slot></div>
<div :style="activeStyle" :class="{active: isActive}"><slot name="item-text"></slot></div>
</div>
</template>
<script>
export default {
name: 'TabBarItem',
props: {
path: String,
activeColor: {
type: String,
default: 'red'
}
},
data() {
return {
isActive: true
}
},
methods: {
itemCilck() {
this.$router.replace();
console.log('itemclick')
}
}
}
</script>
TarBar.vue
<template>
<div id="tab-bar">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'TabBar'
}
</script>
index.js
import { createRouter, createWebHistory } from 'vue-router'
const Home = () => import('../views/home/Home')
const Categroy = () => import('../views/category/Category')
const Cart = () => import('../views/cart/Cart')
const Profile = () => import('../views/profile/Profile')
// import Home from '../views/Home.vue'
const routes = [
{
path: '/home',
component: Home
},
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
the error
vue-router.esm-bundler.js:3266 Error: Invalid route component
at extractComponentsGuards (vue-router.esm-bundler.js:1994)
at eval (vue-router.esm-bundler.js:3107)
the console picture
According to docs they do not omit file types in dynamic imports, try Home.vue instead of just Home:
const Home = () => import('../views/home/Home.vue')
I got my Vue.js application, i've installed vue-router, via npm i vue-router,
i got a router-link on my main page App.vue, and i want to redirect to my Inscription.vue.
I do go on the http://localhost:8080/inscription when i click on the router-link, but the view doesnt change, im still on my main page,
i don't understand why ? (i got no error)
My main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App)
}).$mount('#app')
Vue.use(router);
My router/index.js :
import Vue from 'vue'
import Router from 'vue-router'
import App from '../App.vue'
import Inscription from '../Inscription.vue'
Vue.use(Router)
const routes = [
{
path: '/',
name: 'Home',
component: App
},
{
path: '/inscription',
name: 'Inscription',
component: Inscription
}
]
const router = new Router({
mode: 'history',
routes: routes
});
export default router;
My App.vue simplified
<template>
<div class="acceuil_main">
<div class="navbar_element">
<router-link :to="{name: 'Inscription'}">Inscription</router-link>
</div>
</div>
</template>
<script>
export default {
name: "App",
data () {
return {
beers: null,
connected: false,
user: null
}
}
};
</script>
My Inscription.vue simplified
<template>
<div class="acceuil_main">
<a class="navbar_element">
Inscription
</a>
</div>
</template>
<script>
export default {
name: "Inscription",
data () {
return {
title: "Inscription",
connected: false,
user: null
}
}
};
</script>
a picture of my folder architecture
For router to actually work you need a <router-view> component somewhere in your app. The best place is probably the App component. Check the docs
<router-view> works as a placeholder - router put there the component configured for a route when the route is active.
In that sense your / route should probably not use App component but something else - create another component for example Home which will be displayed on the root route (/)
const App = Vue.component('App', {
name: "App",
template: `
<div class="acceuil_main">
<div class="navbar_element">
<router-link :to="{name: 'Inscription'}">Inscription</router-link>
</div>
<router-view></router-view>
</div>
`
})
const Inscription = Vue.component('Inscription', {
template: `
<div>Inscription</div>`
})
const routes = [{
path: '/inscription',
name: 'Inscription',
component: Inscription
}]
const router = new VueRouter({
mode: 'history',
routes: routes
});
new Vue({
router,
render: h => h(App)
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app"></div>
Im new to Vue JS and I'm making a simple page in Vue JS. Here are my codes:
main.js
import Vue from 'vue'
import App from './App.vue'
import PokeProfile from './components/PokeProfile.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import VueRouter from 'vue-router';
Vue.use(VueRouter)
Vue.use(ElementUI)
const router = new VueRouter({
routes: [
{path: '/', component: App},
{path: '/pokemon/:id', component: PokeProfile},
],
mode: 'history'
})
//Vue.config.productionTip = false
new Vue({
el: '#app',
render: h => h(App),
router: router
})
App.js
<template>
<div id="app">
<div class="tag-group">
<el-tag
v-for="pokemon in pokemons"
:key="pokemon.national_id"
:type="pokemon.name"
effect="plain">
<poke-item :pokemon=pokemon></poke-item>
</el-tag>
</div>
</div>
</template>
<script>
import PokeItem from './components/PokeItem.vue'
import axios from 'axios'
export default {
name: 'app',
components: {
PokeItem
},
data() {
return {
pokemons: []
}
},
created() {
axios.get("http://localhost:3000")
.then(res => this.pokemons = res.data)
.catch(err => {console.log(err)})
}
}
</script>
<style>
div {
display: flex;
justify-content: center;
}
</style>
PokeItem.js
<template>
<div>
<router-link :to="pokemonLink">
{{pokemon.name}}
</router-link>
</div>
</template>
<script>
export default {
data() {
return {}
},
props: {
pokemon: {
type: Object,
required: true
}
},
computed: {
pokemonLink() {
return `/pokemon/${this.pokemon.national_id}`
}
}
}
</script>
PokeProfile.js
<template>
<h1>Hello Pokemon</h1>
</template>
<script>
export default {
}
</script>
The problem here is I can not move to PokeProfile.js when I click on an item in the PokeItem.js file. What could be the problem? I've checked the section of the code related to routing but I didn't see any problem.
Vue-Router uses a dynamic component (<router-view>) to render the components of your routes. Usually you will find this component in the template of your app.vue. Since you have no <router-view> component Vue-Router does not know where to render your route components.
Try this:
// main.js
import Home from './components/Home.vue'
const router = new VueRouter({
routes: [
{path: '/', component: Home},
{path: '/pokemon/:id', component: PokeProfile},
],
mode: 'history'
})
// components/Home.vue
// your old App.vue
// ./App.vue
<template>
<main>
<router-view></router-view>
</main>
</template>
How can I recreate a whole component after button click.
Let say I'm in Component "UserPanel" and there is a button call "Refresh".
When I click that button I would like to destroy component and create it from the scratch. I don't wont to use option like "vm.$forceUpdate()" because it doesn't help in my case.
Is it any way to do it?
My app code:
App.vue:
<template>
<div id="main-cont">
<NavBar></NavBar>
<router-view></router-view>
</div>
</template>
<script>
import NavBar from './components/TopBar/NavBar';
import {mapActions,mapGetters} from 'vuex';
import axios from 'axios';
export default {
name: 'App',
components: {
NavBar,
},
computed:{
...mapGetters(['isLoggedIn'])
},
methods:{
...mapActions(['loadLanguage','setToken','setUserLogged','loadUserProfile'])
},
created(){
this.loadLanguage();
this.setToken();
let userLoggedIn = document.head.querySelector('meta[name="logged"]').content;
if(userLoggedIn){
this.setUserLogged();
this.loadUserProfile();
}
}
}
</script>
<style scoped>
#main-cont{
height: 100%;
}
</style>
main.js:
import Vue from 'vue';
import VueRouter from 'vue-router';
import VueCookie from 'vue-cookie';
import store from './store';
import App from './App';
//Components
import Main from './components/main/Main';
import UserRegister from './components/user/UserRegister';
import ResetPassword from './components/user/ResetPassword';
import UserEdit from './components/user/UserEdit';
import UserView from './components/user/UserView.vue';
import GameMain from './components/game/GameMain';
import GamesList from './components/main/GameList';
import Hall from './components/main/Hall';
import Language from './components/main/Language';
import GameCreate from './components/game/GameCreate';
//Plugins
import langPlugin from './langPlugin';
import VTooltip from 'v-tooltip';
Vue.use(VueRouter);
Vue.use(VueCookie);
Vue.use(langPlugin);
export const router = new VueRouter({
mode: 'history',
routes: [
{path: '/', component: Main},
{path: '/user-register', component: UserRegister},
{path: '/user-edit', component: UserEdit},
{path: '/password-reset', component: ResetPassword},
{path: '/user', component: UserView},
{path: '/game', component: GameMain},
{path: '/game-create', component: GameCreate},
{path: '/games-list', component: GamesList},
{path: '/hall-of-fame', component: Hall},
{path: '/language', component: Language},
]
});
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
Component to Reload.
GameCreate:
<Template>
<div>
<button #click="reloadThisComponent"></button>
</div>
</Template>
<script>
export default{
name: 'GameCreate',
methods:{
reloadThisComponent(){
}
}
}
</script>
Thank you.
EDIT (with the new question details) : Since you're using view router and your component is registred as a route, juste simply add the following to your rebuild method in your Game component this should works fine
this.$router.go(this.$router.currentRoute)
const router = new VueRouter({
mode: 'history',
})
new Vue({
router,
el: '#app',
methods: {
reload: function() {
this.$router.go(this.$router.currentRoute)
}
},
created() {
console.log("Hey")
}
})
<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">
<button #click="reload">Reload</button>
<router-view></router-view>
</div>
The simple way to get to the goal is to set a boolean in v-if on your component. Then switch true/false the boolean. When v-if is false the component is destroyed and will be reinstanciate after.
To do this, there is two way. What we want is to change de state of the parent component that will say if we print our component or not. The first way to do it is by using a State Management plugin like VueX, but it's a little bit too much for what we simply want to do. To be simpliest, we have to trigger an event from your component, that will trigger the state change on the parent.
In the exemple bellow, when you click on the reset button inside MyComponent,custom event named "reset" is emitted. In the parent component, we have a showMyComponent boolean on our MyComponent tag and a listener #reset that will trigger the method named "resetMyComponent" when the event "reset" is emmited by our MyComponent.
Here is a few resources :
What is VueX and State Management if you want know more about the first way to do it
Documentation about Custom Events in VueJS
Doc about Reactivity in Deep, not important here but it's for the explaination of this.nexttick usage here
Hope it's more clear now
var MyComponent = Vue.component('my-component', {
name : "my-component",
template : "#my-component-template",
data(){
return {
interval : null,
count : 0
}
},
created() {
console.log("MyComponent is created")
this.interval = setInterval(() => {
this.count++
},1000)
},
destroyed() {
console.log("MyComponent is destroyed")
clearInterval(this.interval)
}
});
new Vue({
el: "#app",
components : {
MyComponent
},
data: {
showMyComponent : true
},
methods : {
resetMyComponent() {
this.showMyComponent = false;
Vue.nextTick(() => {
this.showMyComponent = true;
});
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<my-component v-if="showMyComponent" #reset="resetMyComponent"></my-component>
</div>
<script type="text/x-template" id="my-component-template">
<div style="padding: 15px">
<p>My component is created since {{count}} seconds</p>
<button #click="$emit('reset')">Reset my component</button>
</div>
</script>
There are multiple ways of recreating components. The most efficient way is to change the component key. What we do here is we will supply a key attribute so Vue knows that a specific component is coupled or tied to a specific piece of data. If the key stays the same, it won't change the component, but if the key changes, Vue knows that it should get rid of the old component and re-create a new one.
Here is a very basic way of doing it:
<template>
<component-to-re-render :key="componentKey" />
</template>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
Every time that forceRerender is called, our 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. this simple and elegant way is solving the most common challenge we face in the Vue app development!
You can also check other possible ways od doing this: https://medium.com/emblatech/ways-to-force-vue-to-re-render-a-component-df866fbacf47
UPDATE:
It magically starts to work later on after I clear browser cache, I do not know what happened before.
I wonder how to include router-view in a component rather than directly use it?
For Example:
main.js
import Vue from 'vue'
import router from "./router"
import App from './App.vue'
new Vue({
el: '#app',
render: h => h(App),
router
})
App.vue
<template>
<div>
<page-content></page-content>
</div>
</template>
<script>
import pageContent from "./page-content.vue";
export default {
name: 'app',
data() { return {}; },
components: {
"page-content": pageContent
}
}
</script>
page-content.vue
<template>
<div class="page-content">
<router-view>
</router-view>
</div>
</template>
<script>
export default {
}
</script>
router.js
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const routes = [{ path: '/', component: Foo }]
export default new VueRouter({
routes
})
The problem is I can not see foo shown up on page