New Router page doesn't load the component until manually reloading the entire page - vue.js

I added a new route, and added an animation in transition to the new route.
I added the following code which pushes the new route (/first) when a button is clicked:
/* From the Template */
<router-view #clickedNext1="onClickTransition" v-slot="{ Component }">
<transition name="route1" mode="out-in">
<component :is="Component"></component>
</transition>
</router-view>
/* From the Script */
methods: {
onClickTransition() {
this.$router.push("/first");
},
Now the problem is that when I click the button and invoke the "onClickTransition" method, the router seems to be pushed just fine, but the page is empty. The components are rendered only when I manually refresh the page by pressing ctrl+R.
I believe the problem perhaps comes from insertion of the animation, but if I refresh the page manually, the animation works just fine. So I have no idea what the problem is. I will be very grateful for help.
Here is the rest of the code for app.vue:
<template>
<router-view #clickedNext1="onClickTransition" v-slot="{ Component }">
<transition :key="$route.fullPath" name="route1" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</template>
<script>
export default {
name: "App",
components: {},
data() {
return {};
},
methods: {
onClickTransition() {
this.$router.push("/first");
},
leave(event) {
event.preventDefault();
event.returnValue = "";
},
},
mounted() {
window.addEventListener("beforeunload", this.leave);
},
beforeUnmount() {
window.removeEventListener("beforeunload", this.leave);
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #fff;
background-color: #151515;
position: relative;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0px;
}
/* route transition */
.route1-enter-from {
opacity: 0;
}
.route1-enter-active {
transition: all 3s ease-in;
}
.route1-leave-to {
opacity: 0;
}
.route1-leave-active {
transition: all 3s ease-in;
}
</style>
code section from index.js:
import { createRouter, createWebHistory } from "vue-router";
import MainPage from "../views/MainPage.vue";
import FirstScene from "../views/FirstScene.vue";
const routes = [
{
path: "/",
name: "main",
component: MainPage,
},
{
path: "/first",
name: "first",
component: FirstScene,
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
The "onClickTransition" method comes from the "PreStartPage.vue" component, which is the child component of "MainPage.vue" which is the main route.
Once the "Next" button is clicked in the "PreStartPage.vue", it sends an event to the "MainPage.vue" with this.$emit."MainPage.vue" then receives the event with a method named "onClickNext1", which sends out a signal to "App.vue" with another this.$emit. That is where the "#clickedNext1" that is shown in the App.vue comes from.
Here is the code from "PreStartPage.vue":
<script>
export default {
name: "PreStartPage",
methods: {
onClickNext() {
this.$emit("clickedNext");
},
},
};
</script>
Here is the code from "MainPage.vue":
<script>
import PreStartPage from "../components/PreStartPage.vue";
export default {
name: "MainPage",
components: { PreStartPage },
data() {
return { showMain: true, showPre: false };
},
methods: {
toggleMain() {
this.showMain = !this.showMain;
this.showPre = !this.showPre;
},
onClickNext1() {
this.$emit("clickedNext1");
},
},
};
</script>

Try modifying your code like this:
/* From the Template */
<router-view #clickedNext1="onClickTransition" v-slot="{ Component }">
<transition :key="$route.fullPath" name="route1" mode="out-in">
<component :is="Component"></component>
</transition>
</router-view>
The "key" property set to $route.fullPath should ensure that the transition is done correctly whenever the route is changed.
EDIT
To solve this, you can add a ":enter-active-class" and ":leave-active-class" property to the transition component, which allows you to specify the class that should be applied to the element during the transition.
In your App.vue component, you can update the transition component like this:
<transition :key="$route.fullPath" name="route1" mode="out-in" :enter-active-class="'route1-enter-active'" :leave-active-class="'route1-leave-active'">
<component :is="Component" />
</transition>
This will ensure that the correct classes are applied to the element during the transition, and that the components are fully rendered before the animation starts.
For more info i should you to visit the official wiki: https://vuejs.org/guide/built-ins/transition.html#css-based-transitions

You can try using
created() {
this.$watch(() => this.$route.params, () => {
// WATCH FOR ROUTE CHANGES
},
}

Related

How to change the colour of multiple children components when triggered by a single child component in Vue

how do you affect multiple (in this case just 2) children components owned by two different parent components when an action is triggered by one of the children components?
For example I have a component, lets call it <component-one/>. Inside this component I have something like below:
<div #mouseover="hover=true" #mouseleave="hover=false" :class="setColour">
<div class="icon-wrapper commercial-layout position-relative">
<u-button icon color="transparent" #click="toggleCommercials">
<u-icon :icon="icon" color="white"/>
</u-button>
<small class="commercial-ind">COMMERCIAL ADS</small>
<div class="commercial-layout commercial-ind">{{hide}}</div>
</div>
</div>
computed: {
setColour () {
if (this.hover) {
return 'bg-danger'
}
else if (this.commercials) {
return 'bg-primary'
}
else if (!this.commercials) {
return 'bg-secondary'
}
},
watch: {
setColour: function(val) {
console.log("val",val)
}
}
But somewhere else in the code base I have two other components, lets call them <component-two/> and <component-three/>. Inside those components I use component-one. When I push on the button from component-two I want the same effect to also be triggered in component-three, and vice versa, but I'm not quite sure how to achieve that.
Currently both component-two and component-three just have component-one. I've tried adding a watch in component-one but it doesn't really do anything other than capturing changes to the setColour computed property. (I naively thought by capturing the change, all places where component-one is used will get updated)
I'm not sure I totally understand your specific component relationships, but in general I recommend using Vuex.
Using Vue 2 and the CLI, I created sample SFCs that use Vuex to store the background color CSS style. Each child is associated with a specific color, and clicking it's button updates the color of all sibling components.
/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
commonBgColor: 'navajowhite'
},
mutations: {
updateBgColor(state, newColor) {
state.commonBgColor = newColor;
}
}
})
Parent.vue
<template>
<div class="parent">
<child initBgColor="aquamarine" instanceName="One" />
<child initBgColor="mediumorchid" instanceName="Two" />
</div>
</template>
<script>
import Child from './Child.vue'
export default {
components: {
Child
}
}
</script>
Child.vue
<template>
<div class="child">
<div class="row">
<div class="col-md-6" :style="currentBgColor">
<span>Sibling Component {{ instanceName }}</span>
<button type="button" class="btn btn-secondary" #click="updateCommonBgColor">Change All Sibling Colors</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
initBgColor: {
type: String,
required: true
},
instanceName: {
type: String,
required: true
}
},
data() {
return {
backgroundColor: this.initBgColor
}
},
computed: {
currentBgColor() {
return 'background-color: ' + this.$store.state.commonBgColor;
}
},
methods: {
updateCommonBgColor() {
this.$store.commit('updateBgColor', this.backgroundColor);
}
}
}
</script>
<style scoped>
.child {
margin-top: 0.5rem;
}
span {
font-size: 1.5rem;
padding: 0.5rem;
}
button {
float: right;
}
</style>

Vue v-cloak directive does not work on component

I added v-cloak directive on the top div in my component and added css as in docs, set a timeout but it does not work I can see else content and the form styles.
Component code:
<div class="form-in-wrap" v-cloak>
<ul id="example-1" v-if="reports.length>0">
<report-component v-for="report in reports" :report="report" :key="report.id" :options="options">
</report-component>
</ul>
</div>
Component script:
<script>
import ReportComponent from "./ReportComponent";
export default {
components: {ReportComponent},
data: function () {
return {
reports: {
type: Array,
},
report: {
...
},
}
},
created: function () {
var self = this
setTimeout(function () {
self.loadData('/reports')
}, 2000);
},
methods: {
loadData: function() {
get method
}
}
</script>
<style scoped>
[v-cloak] {
display: none !important;
}
</style>
All example creates a Vue instance, but I am using export default in my component. Also it is not a main component, it is included with router can this be the case why it does not work?

Error when trying to add created method in Vue component

Component
<template lang="html">
<div class="chat-log">
<chat-message v-for="message in messages" :message="message"></chat-message>
</div>
</template>
<script>
export default {
props: ["messages"]
}
</script>
<style lang="css">
.chat-log .chat-message:nth-child(even) {
background-color: #ccc;
}
.chat-log {
overflow-y: auto;
max-height: 400px;
}
</style>
When I change the above script code to below. I get errors..
<script>
export default {
props: ["messages"]
},
created() {
$(".chat-log").scrollTop($(".chat-log").prop('scrollHeight'));
}
</script>
Error Details
Unexpected token, expected ;
Issue comes only when adding the created method, Am I missing anything?
The created lifecyle method goes within the body of the Vue component itself, not outside. I mean:
export default {
props: ["messages"],
created() {
$(".chat-log").scrollTop($(".chat-log").prop('scrollHeight'));
}
}
Vue.js Lifecycle
Your created(){} method should be encapsulated within your export default {} block.
In other words, change your code this:
export default {
props: ["messages"],
created() {
$(".chat-log").scrollTop($(".chat-log").prop('scrollHeight'));
}
},

How to pass state between components?

I have a component called "item-detail" that has a "item" prop on it, like so:
<item-detail v-ref="itemDetail" v-if="showItemDetail" v-on:clicked="showItemDetail = false"></item-detail>
Then I have an item view component like this:
<item-view v-on:click="onItemClick(this)" title="head" :item="equipment.head"></item-view>
I'm trying to make it so that when the item-view click event fires, it passes that views "item" prop over to the item-detail component. So my onItemClick looks like this:
onItemClick: function(item) {
this.$refs.itemDetail.item = item;
appState.showItemDetail = true;
}
I can't see from the docs how to get a handle to the item-view inside that v-on:click attribute. "this" always equates to the Vue app instance inside the onItemClick method and "item" is also set to the Vue app instance.
Basically the use case is "When the item view is clicked, pass its 'item' property value to the item-detail component and display the item-detail component.".
After having a conversation I proposed that using Vuex was more suitable that passing logic up the chain which could lead to lots of scaling problems later in the project life cycle.
https://github.com/LiamDotPro/VuexStoreExample
This example shows how you can use a store to easily pass logic between components without direct relationships or chaining.
store
/* eslint-disable space-before-function-paren */
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
titleText: "hmm.."
}
const actions = {
changeHomeText: ({commit}, context) => {
commit('UPDATE_TEXT', context)
}
}
const mutations = {
UPDATE_TEXT(state, text) {
state.titleText = text
}
}
const getters = {}
export default new Vuex.Store({
strict: true,
state,
getters,
actions,
mutations
})
app
<template>
<div id="app">
<router-view/>
<div>
<h1>{{getTitle}}</h1>
</div>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
msg: '',
}
},
computed: {
getTitle: function () {
return this.$store.state.titleText;
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Inner component
<template>
<div class="innerTile">
<h1>Inner Tile</h1>
<button #click="ChangeTileText()">inner tile button..</button>
</div>
</template>
<script>
export default {
name: '',
data() {
return {
msg: '',
}
},
methods: {
ChangeTileText: function () {
this.$store.dispatch("changeHomeText", "Hi from inner tile..");
}
}
}
</script>
<style scoped>
</style>

Building reusable components with custom methods

I am trying to build a reusable tab component with vuejs. I am still learning some basic consepts of vue and hardly managed to finish tab generation and switch logic. It is OK to switch between tabs in component itself now. But I have some problems with making my component to listen outside triggers.
For now, I can switch my tabs outside of the component with the help of $refs. But as I am trying to make it reusable this method doesn't sound practical. What should I do?
Here is the JSFiddle
Vue.component('tabs', {
template: `
<div class="tabs">
<div class="tab-titles">
<a :class="{'active':tab.active}" v-for="tab in tablist" href="#" class="tab-title" #click.prevent="activateTab(tab)">{{tab.title}}</a>
</div>
<div class="tab-contents">
<slot></slot>
</div>
</div>
`,
data() {
return {
tablist: [],
}
},
methods: {
activateTab: function(tab) {
this.tablist.forEach(t => {
t.active = false;
t.tab.is_active = false
});
tab.active = true;
tab.tab.is_active = true;
},
activateTabIndex: function(index) {
this.activateTab(this.tablist[index]);
},
collectTabData: function(tabData) {
this.tablist.push(tabData)
}
},
});
//==============================================================================
Vue.component('tab', {
template: `
<div :class="{'active':is_active}" class="tab-content">
<slot></slot>
</div>
`,
data() {
return {
is_active: this.active
}
},
mounted() {
this.$parent.collectTabData({
tab: this,
title: this.title,
active: this.active,
is_active: this.is_active
});
},
props: {
title: {
type: String,
required: true
},
active: {
type: [Boolean, String],
default: false
},
}
});
//==============================================================================
Vue.component('app', {
template: `
<div class="container">
<tabs ref="foo">
<tab title="tab-title-1">
<h3>content-1</h3>
Initial content here
</tab>
<tab title="tab-title-2" active>
<h3>content-2</h3>
Some content here
</tab>
<tab title="tab-title-3">
<h3>content-3</h3>
Another content here
</tab>
</tabs>
<a href="#" #click='switchTab(0)'>switch to tab(index:0)</a>
</div>
`,
methods: {
switchTab: function () {
vm.$children[0].$refs.foo.activateTabIndex(0);
}
},
});
//==============================================================================
const vm = new Vue({
el: '#inner-body',
});
#import url('https://fonts.googleapis.com/css?family=Lato');
#inner-body{
font-family: 'Lato', sans-serif;
background-color:#ffffff;
padding:20px;
}
.tab-titles {
}
.tab-title {
display: inline-block;
margin-right: 10px;
color: #bbb;
text-decoration: none;
}
.tab-title.active {
color: #06cd92;
border-bottom:1px solid #06cd92;
}
.tab-contents{
border: 1px solid #ddd;
border-width:1px 0;
margin-top:-1px;
margin-bottom:20px;
}
.tab-content {
display: none;
padding-bottom:20px;
}
.tab-content.active {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.5/vue.min.js"></script>
<div id="inner-body">
<app></app>
</div>
Vue Components are supposed communicate one-way down to children via Props (where only parents mutate the props on the children, never the children themselves) and children communicate to parents by emitting events. The makes nesting components much easier to reason about, and decouples components properly. So what do you do when the parent wants to change a tab? Let me walk you through a process:
1) Imagine we add a prop called activeTab to the tabs component (I'm not following your code in your question directly here, just basing loosely off it to demonstrate the process easier). The parent will change the value of this prop whenever it wants. The tabs component (aka child component in this case) should not alter the value of the activeTab prop. Instead, inside the tabs component, add a watcher for this prop:
in child component (ie. tabs)
props: {
/**
* Prop parent can use to set active tab
*/
'activeTab': {
type: Number
}
},
watch: {
/**
* Watch for changes on the activeTab prop.
*
* #param {boolean} val The new tab value.
* #param {boolean} oldVal The old tab value.
* #return {void}
*/
activeTab: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
// do something here to make the tabs change
// and never alter the prop itself.
this.switchTab(val)
}
},
2) Now on your parent, you should have a reactive data property that can be named the same as your prop if you want:
in parent component (ie. app)
data: {
activeTab: 0
}
3) Then we need to make it where when you alter the data property above, the prop gets altered too. Here's what it would look like on the tabs component tag:
in parent component (ie. app)
<tabs :activeTab="activeTab" ...
4) What do you do if you want to allow the tabs component to also alter the active tab sometimes? Easy, just emit an event whenever an active tab is changed:
in the child component (ie. tabs)
methods: {
switchTab (newActiveTabValue) {
// store the new active tab in some data property in this component, but not the "activeTab" prop, as mentioned earlier.
this.whatever = newActiveTabValue
// ... now emit the event
this.$emit('switched', newActiveTabValue)
}
5) Your parent should now listen for this emitted event and update its own data property:
in parent component (ie. app)
<tabs :activeTab="activeTab" #switched="activeTab = arguments[0]" ...
Seems a little bit more effort, but it's all worth it as your app grows in complexity and more things become nested. You can read more on Composing Components in the official docs here.