I am developing an e-commerce solution using Vue.js (with Vuex and Vue Router). I have noticed that, on seemingly random occasions, my header and footer components will not load either after a page refresh, or when loading the page.
Here is my App.vue
<template>
<div id="app">
<VGLHeader></VGLHeader>
<div class="site-view">
<transition name="slither">
<router-view :key="$route.params" />
</transition>
</div>
<VGLFooter></VGLFooter>
</div>
</template>
<script>
// import { mapActions } from 'vuex';
import VGLHeader from '#/components/VGLHeader.vue'
import VGLFooter from '#/components/VGLFooter.vue'
export default {
name: 'App',
components: {
VGLHeader,
VGLFooter
}
}
</script>
<style>
#app {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.site-view {
flex:1;
}
.slither-enter-active, .slither-leave-active {
transition: transform 300ms;
}
.slither-enter, .slither-leave-to {
transform: translateX(-100%);
}
.slither-enter-to, .slither-leave {
transform: translateX(0);
}
</style>
VGLHeader and VGLFooter have several nested components themselves, while they also fetch data from my API (such as contact data that I want the client to be updating in the database via an admin panel, hence why I am fetching data in them). And yes, I know I'm not "supposed" to put $route.params in the "key" part, but it's just a temporary makeshift patch to solve another issue that I'm keeping until I find a better solution later on.
Related
My project is separated into parts, and some parts got so big I even separated them using composition on sub components.
The problem is, the compose components are rendering, but aren't interactive, for instance, here's a menu component that contains a header with a button:
import React from 'react';
export default function Menu() {
// This is responsible of creating a custom button with bunch of things in it, that button should rotate 180deg when hovered.
function IconButton() {
return (
<div class='rotating_button'>
// Bunch of code...
<button></button>
// Bunch of other code...
</div>
);
}
return (
<div class='menu'>
<IconButton />
// The rest of the Menu Code.
</div>
);
}
Everything till now seems fine, but when I wanna make a compose component interactive through something like CSS or other JavaScript file, it doesn't animate, or it doesn't render as expected.
Here's my CSS my code that in this case should make the custom button rotatable when hovered, and it's so simple it doesn't work:
.rotating_button {
/* All of this works. */
background_color: red;
width: 50px;
height: 50px;
transform: rotate(0deg);
/* But this does not. */
transition: 0.5s;
}
.rotating_button:is(:hover, :focus) {
transform: rotate(180deg);
}
To summarize, it rotates, but with no transition applied on it.
But when it's not a compose component, and it's directly put in the return section of the Menu Component, it works just write.
Suchlike this code:
import React from 'react';
export default function Menu() {
return (
<div class='menu'>
<div class='rotating_button'>
// Bunch of code...
<button></button>
// Bunch of other code...
</div>
// The rest of the Menu Code.
</div>
);
}
I have a sidebar component that works similar to a modal. When a button is clicked, the sidebar translates into the viewport with nav links. These nav links are actually router-links that are wired up to a vue-router.
What I'm trying to accomplish
When I click on a router-link that is inside my sidebar component, I want the sidebar to transition off the viewport and I want the clicked router-link's component to render without the page reloading.
What's currently happening
When I click on the router-link, the sidebar is removed instantly from the DOM. It does not translate off the screen as intended. Also, the page is reloaded.
What else have I tried
I also tried moving the <transition> wrapper inside TheSidebar.vue component along with the associated CSS classes, and I passed sidebarIsVisible as a prop from App.vue to TheSidebar.vue.
My code
A Codesandbox demo can be found here
App.vue
<template>
<router-view></router-view>
<button #click="toggleSidebar" class="toggleBtn">Toggle Sidebar</button>
<transition name="sidebar">
<the-sidebar
v-if="sidebarIsVisible"
#link-clicked="toggleSidebar"
></the-sidebar>
</transition>
</template>
<script>
import TheSidebar from "./components/TheSidebar.vue";
export default {
components: {
TheSidebar,
},
data() {
return {
sidebarIsVisible: false,
};
},
methods: {
toggleSidebar() {
this.sidebarIsVisible = !this.sidebarIsVisible;
},
closeSidebar() {
this.sidebarIsVisible = false;
},
},
};
</script>
<style>
/* basic styling */
.toggleBtn {
position: fixed;
top: 5px;
left: 5px;
}
.sidebar-enter-active {
animation: slide-sidebar 0.3s ease;
}
.sidebar-leave-active {
animation: slide-sidebar 0.3s ease reverse;
}
#keyframes slide-sidebar {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
</style>
TheSidebar.vue
<template>
<div class="sidebar">
<nav>
<ul>
<li>
<router-link #click="$emit('link-clicked')" to="/link1">
Link 1
</router-link>
</li>
<li>
<router-link #click="$emit('link-clicked')" to="/link2">
Link 2
</router-link>
</li>
</ul>
</nav>
</div>
</template>
<script>
export default {
emits: ["link-clicked"],
};
</script>
<style scoped>
/* basic styling */
</style>
main.js
import { createApp } from "vue";
import { createRouter, createWebHistory } from "vue-router";
import App from "./App.vue";
import LinkOne from "./components/LinkOne.vue";
import LinkTwo from "./components/LinkTwo.vue";
const app = createApp(App);
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/link1", component: LinkOne },
{ path: "/link2", component: LinkTwo }
]
});
app.use(router);
app.mount("#app");
There are a couple of things I'm unsure about here but I'll try and explain what I think is happening.
Firstly, the click event on the router-link is what's causing the page to reload, but I can't find anything in the docs mentioning this as expected behaviour (it might be worth opening an issue on the GitHub repo).
The fix for this is to use event-delegation by moving the event-handler onto the ul and creating a method to determine if a link has been clicked (example below).
Secondly, and this is where things get weird, in VSCode, using kebab-case in the child components' emitted event seems to prevent anything from being emitted, so changing them to camelCase fixes this. But, trying this in CodeSandbox simply doesn't work, and ESLint complains that the emitted event should be kebab-case. So, in CodeSandbox, the opposite is true: the emitted event names should be kebab-case and the listener should be camelCase! Absolutely no idea why as this goes against what the docs say on casing:
...we recommend using kebab-cased event listeners when you are using in-DOM templates.
Again, I can't find anything in the docs explicitly saying you need to use camelCase when emitting an event, it just says kebab-case is preferred when listening for an event.
So, all in all, for your code to work in VSCode and in a way which follows what is recommended by the docs, you need to change it to this:
<template>
<div class="sidebar">
<nav>
<!-- Move event here -->
<ul #click="handleClick($event)">
<li>
<router-link to="/link1">
Link 1
</router-link>
</li>
<li>
<router-link to="/link2">
Link 2
</router-link>
</li>
</ul>
</nav>
</div>
</template>
<script>
export default {
emits: ['linkClicked'], // Use camelCase for emitting
methods: {
handleClick(e) {
// Check the target is a link being clicked
if (e.target.localName !== 'a') return
this.$emit('linkClicked')
}
}
}
</script>
Keep App.vue exactly as you have it already and it should work.
For your code to work in CodeSandbox, swap the casing:
...
emits: ['link-clicked'], // Use kebab-case for emitting
...
this.$emit('link-clicked')
...
App.vue:
#linkClicked="toggleSidebar"
Working example.
If anyone could shed some light on this, it'd be great as I'm completely stumped on what's happening here.
Good day everyone! I'm currently learning vue and I'm following a youtube tutorial on building an image carousel from scratch. Here's the code for the Image Carousel parent component:
<template>
<div class = "slides">
<div class = "slides-inner">
<div v-for= "slide in slides">
<Slide v-bind:slide= "slide"></Slide>
</div>
</div>
</div>
<script>
import Slide from './Slide';
export default{
data(){
return{
slides:[
{ src:'src/assets/slide1.jpg' },
{ src:'src/assets/slide2.jpg' },
{ src:'src/assets/slide3.jpg' },
{ src:'src/assets/slide4.jpg' },
{ src:'src/assets/slide5.jpg' }
]
}
},
components: {
Slide
}
}
</script>
<style scoped>
.slides {
align-items: center;
background-color: #666;
color: #999;
display: flex;
font-size: 1.5rem;
justify-content: center;
min-height: 10rem;
}
</style>
and here's the code for the individual image slides:
<template>
<div class="slide">
{{slide.src}}
</div>
</template>
<script>
export default {
data(){
return{}
},
props: {
slide: ['slide']
}
}
</script>
<style scoped>
</style>
The v-bind on the v-for loop on the parent Image Carousel component is supposed to bind the slide.src property to the current slide being looped so that it will display the image in the browser but what I'm getting is a blank browser screen and an error that says the right value of the operator at v-bind is not an object although the tutorial I'm following works exactly as it should with this same code so I'm wondering what I'm doing wrong.
In your slide component, it should be
export default {
data(){
return{}
},
props: {
slide: Object
}
}
You can check valid props type in Vue document
Please also see the Vue Style Guide as prop definitions should be as detailed as possible. An Essential Requirement by the creators of Vue. See Vue Style Guide.
I am working on a simple Vue app, using vue-cli and webpack for that purpose.
So basicly i have 2 components, a parent and a child component ~
like this:
<template>
<div class="triPeaks__wrapper">
<div class="triPeaks">
<tri-tower class="tower"></tri-tower>
<tri-tower class="tower"></tri-tower>
<tri-tower class="tower"></tri-tower>
</div>
<div class="triPeaks__line">
<tower-line :towerLine="towerLineCards" />
</div>
<tri-pack />
</div>
</template>
the towerLineCards is the important thing there, it is a prop that is passed to the tower-line component, it is basicly a array with 10 elements, it is a array with 10 numbers that are shuffled, so it can be something like that:
[1,5,2,6,8,9,16,25,40,32]
this array is create via beforeMount on the lifecycle.
On the child component:
<template>
<div class="towerLine-wrapper">
<div class="towerLine">
<img v-for="index in 10" :key="index" class="towerLine__image" :src="getImage(index)" alt="">
</div>
</div>
</template>
<script>
export default {
props: {
towerLine: {
type: Array,
required: true
}
},
method: {
getImage (index) {
return '#/assets/images/cards/1.png'
}
}
}
</script>
<style lang="scss">
.towerLine {
display: flex;
position: relative;
top: -90px;
left: -40px;
&__image {
width: 80px;
height: 100px;
&:not(:first-child) {
margin-left: 3px;
}
}
}
</style>
the issue is with the :src image that i am returning via the getImage(), this way it is not working. If i change to just src it works just fine, i did this way just to test, because the number in the path should be dynamic when i got this to work.
What is wrong with this approach? any help?
Thanks
Firstly, you should use a computed property instead of a method for getImage().
And to solve the other problem, you could add require(YOUR_IMAGE_PATH) when you call your specific image or put it inside /static/your_image.png instead of #/assets/images/cards/1.png.
I just made my first project with VueJS and Vue-loader.
So I made my first component to show a simple message, it works fine when I make one message, but I get an error when I make multiple messages:
(Emitted value instead of an instance of Error)
Error compiling template:
<message>This is a small message!</message>
<message>Another one</message>
- Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.
This is my code. I'm very new to this and I can't figure out what's wrong.
App.vue
<template>
<message>This is a small message!</message>
<message>Another one</message>
</template>
<script>
import Message from './Components/Message.vue';
export default {
name: 'app',
components: {
Message,
},
data () {
return {
}
}
}
</script>
Message.Vue
<template>
<div class="box">
<p>
<slot></slot>
</p>
</div>
</template>
<script>
export default {
}
</script>
<style>
.box { background-color: #e3e3e3; padding: 10px; border: 1px solid #c5c5c5; margin-bottom: 1em;}
</style>
I hope somebody can help!
The error is pretty self-explanatory. You should have only one root element in each component. So just pack everything in a div.
<template>
<div>
<message>This is a small message!</message>
<message>Another one</message>
</div>
</template>