Reusable TransitionGroup component doesn't work as expected - vue.js

I have a very simple reusable transition group component. Unfortunattely it doesn't work as expected.
Expected behavior: wrap li tag into TransitionGroup --> animation works.
Actual behavior: wrap li tag into TransitionGroup and add transition styles into that component --> animation works.
TransitionGroup component:
<template>
<transition-group name="list" tag="ul">
<slot></slot>
</transition-group>
</template>
<script>
export default {
props: {
duration: {
type: Number,
required: false,
default: 2,
},
},
};
</script>
<style scoped>
.list-enter-from {
opacity: 0;
}
.list-enter-active {
transition: opacity 1s ease-in;
}
.list-enter-to {
opacity: 1;
}
</style>
Then I'm trying to use it in ProductList component and it doesn't work:
<template>
<fade-transition-group class="products-list">
<li
class="products-list__item"
v-for="product in productsToRender"
:key="product.id"
>
<card
:image="product.image"
:price="product.price"
:title="product.title"
:rating="product.rating.rate"
:id="product.id"
></card>
</li>
</fade-transition-group>
</template>
But if I add transition styles in ProductsList component then it works. Why? How to fix that?
If I add these styles to ProductList component styles section then everything works and it's not clear why:
.list-enter-from {
opacity: 0;
}
.list-enter-active {
transition: opacity 1s ease-in;;
}
.list-enter-to {
opacity: 1;
}

Related

Stopping an animation with an click event in Vuejs?

I have a MainMenuItem that has a bunch of static titles. There is one variable item that is for news and events and that has a horizontal ticker, scroll animation on it. This animation is happening inside of the MainMenuItem component, but the click event will be happening from the parent. The click event currently needs to stop the animation and change the font weight (can be seen in the readLink class).
Wth Vue I cannot mutate the prop type itself with something like #click="itemType = ''".
So how can I turn this ticker animation off on click?
Basically, all I would need to do is make the MainMenuItem's itemType = '' and make it the same as every other item. Do I need to make a data type? Or a click event that $emit's an event to the child/base component MainMenuItem?
If there is anymore code that is necessary please let me know!
Cheers!
MainMenuItem.vue (base component with the ticker animation )
<template>
<q-btn align="left" dense flat class="main-menu-item" v-on="$listeners">
<div class="flex no-wrap items-center full-width">
<iconz v-if="iconz" :name="iconz" type="pop" color="black" class="mr-md" />
<q-icon :name="menuIcon" />
<div v-if="itemType === 'news'" class="_ticker">
<div class="ellipsis _ticker-item">{{ title }}</div>
</div>
<div v-else>
<div class="ellipsis">{{ title }}</div>
</div>
<q-icon name="keyboard_arrow_right" class="_right-side" />
</div>
</q-btn>
</template>
<style lang="sass" scoped>
// $
.main-menu-item
display: block
font-size: 15px
position: relative
width: 100%
border-bottom: 1px solid #F5F5F5
+py(10px)
._left-side
color: #000000
._ticker
font-weight: bold
margin-left: 2em
width: 83%
white-space: nowrap
overflow: hidden
position: absolute
&-item
display: inline-block
padding-left: 100%
animation: ticker 8s linear infinite
#keyframes ticker
to
transform: translateX(-100%)
</style>
<script>
import { iconz } from 'vue-iconz'
export default {
name: 'MainMenuItem',
components: { iconz },
props: {
comingSoonShow: { type: Boolean, default: false },
title: { type: String, default: 'menu' },
subtitle: { type: String, default: '' },
menuIcon: { type: String, default: '' },
iconz: { type: String, default: '' },
itemType: { type: String, default: '' },
}
}
</script>
MainMenu.vue (just the location I am using the base component)
<template>
<MainMenuItem
v-for="news in newsList"
:key="news.id"
#click="openNews(news)"
:title="news.title"
itemType="news"
:class="{ readLink: readNewsList[news.id] }"
menuIcon="contactless"
></MainMenuItem>
</template>
export default {
name: 'MainMenu',
methods: {
openNews(postInfo) {
dbAuthUser().merge({ seenNewsPosts: { [postInfo.id]: true } })
Browser.open({ url: postInfo.url, presentationStyle: 'popover' })
}
},
computed: {
readNewsList() {
return this.authUserInfo?.seenNewsPosts || {}
},
}
<style lang="sass" scoped>
.readLink
font-weight: 500
</style>
Since it's a prop the simplest way is to emit an event that executes what you need in the parent and also bind a variable not static prop to be able to change it. example in the child :
<button #click="onClickButton">stop animation </button>
export default {
methods: {
onClickButton (event) {
this.$emit('stopAnimation')
}
}
}
And in the parent you listen to it as so:
<MainMenuItem
v-for="news in newsList"
:key="news.id"
#click="openNews(news)"
:title="news.title"
:itemType="itemType"
:class="{ readLink: readNewsList[news.id] }"
menuIcon="contactless"
v-on:stopAnimation="itemType = ''"
></MainMenuItem>
export default {
data() {
return {
itemType: "news",
}
}
}
}

Nuxt - Using router.push inside a component not changing pages correctly

index.vue -
<template>
<div>
<Header />
<div class="container">
<SearchForm />
</div>
</div>
</template>
<script>
const Cookie = process.client ? require('js-cookie') : undefined
export default {
data() {
return {
form: {
email: '',
password: ''
},
show: true
}
},
methods: {
logout() {
// Code will also be required to invalidate the JWT Cookie on external API
Cookie.remove('auth')
this.$store.commit('setAuth', {
auth: null,
user_type: null
})
}
}
}
</script>
<style>
.container {
/* margin: 0 auto; */
/* min-height: 100vh; */
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
</style>
jobs.vue -
<template>
<div>
<Header />
<SearchForm />
<b-container class="main_container">
<b-row>
<h1> Results for "{{q}}"</h1>
</b-row>
<b-row>
<ul id="array-rendering">
<li v-for="item in results" :key="item.job_id">
{{ item.job_title }}
{{ item.job_city }}
{{ item.job_state }}
{{ item.job_work_remote }}
</li>
</ul>
</b-row>
</b-container>
</div>
</template>
<script>
const Cookie = process.client ? require('js-cookie') : undefined
export default {
// middleware: 'notAuthenticated',
watchQuery: ['q'],
data() {
return {
q: null,
results: []
}
},
async fetch() {
this.q = this.$route.query.q
this.results = await this.$axios.$get('/api/job/search', {
params: {
keyword: this.q,
}
})
},
methods: {
}
}
</script>
<style>
.container {
align-items: center;
text-align: center;
}
</style>
SearchForm.vue component -
<template>
<div id='searchFormDiv'>
<b-form inline #submit.prevent="onSubmit">
<label class="sr-only" for="inline-form-input-name"> keyword</label>
<b-form-input v-model="form.keyword" id="inline-form-input-name" class="mb-2 mr-sm-2 mb-sm-0" placeholder="Job title or keyword" size="lg"></b-form-input>
<label class="sr-only" for="inline-form-input-username">location</label>
<b-input-group class="mb-2 mr-sm-2 mb-sm-0">
<b-form-input v-model="form.location" id="inline-form-input-username" size="lg" placeholder="City, state or zip"></b-form-input>
</b-input-group>
<b-button type="submit" variant="primary">Find Jobs</b-button>
</b-form>
</div>
</template>
<script>
import {
BIconSearch,
BIconGeoAlt
} from 'bootstrap-vue'
export default {
data() {
return {
form: {
keyword: '',
location: ''
}
}
},
created () {
this.form.keyword = this.$route.query.q
},
methods: {
onSubmit() {
this.$router.push({
path: 'jobs',
query: {
q: this.form.keyword
}
});
}
},
components: {
BIconSearch,
BIconGeoAlt
},
}
</script>
<style>
#searchFormDiv {
margin-top: 50px
}
</style>
The route for "http://localhost:3000/" returns the index.vue page.
In this vue page, I have a component with a search form. Once you complete these form and hit the seach button, it will re-direct to a results page
if this.form.keyword = "Data", the next URL will be "http://localhost:3000/jobs?q=Data" and it will be using the jobs.vue page.
The issue I'm running into is the CSS is not being loaded from the jobs.vue page. It's still coming from the index.vue page for some reason. If I refresh the page, then the CSS from jobs.vue is loading. I need the CSS to load from jobs.vue on the initial redirect. All of the query data is working as expected so thats a plus.
However, the following CSS is being applied from index.vue for some reason instead of the CSS from the jobs.vue page -
display: flex;
justify-content: center;
Does anyone know whats going on here? This app is SSR and not SPA.
You have to scope your css from the index.vue page to the other pages with the scoped directive (see docs https://vue-loader.vuejs.org/guide/scoped-css.html)
<style scoped>
/* local styles */
</style>
<style>
/* global styles */
</style>
You can add your global CSS in your layouts/default.vue file.
This solved the issue -
methods: {
onSubmit() {
window.location = 'http://localhost:3000/jobs?q=' + this.form.keyword;
}
},

VueJS: Chrome devtools doesn't instantly update an array (in parent component). Why?

In the following code, I'm trying to get selected users from a list of given users (users array) via checkbox input (defined in a child component) in an array selectedUsers (defined in parent).
The problem is when I check/select the user, devtools doesn't update on first reaction. I've to navigate away from the selected component in devtools and then comeback to see the updated array.
app.vue (parent)
<template>
<div class="container">
<div class="row">
<div v-for="(user,idx) in users" class="col-xs-12 col-md-6 col-md-offset-3">
<child :user="user" :userIdx="idx" :selectedUsers="selectedUsers" /> <span>{{user.name }}</span>
</div>
</div>
</div>
</template>
<script>
import child from './child.vue'
export default {
data: function() {
return{
users: [
{id: 1, name: 'Allen'},
{id: 2, name: 'Jack'},
{id: 3, name: 'Obama'},
{id: 4, name: 'Donald'},
{id: 5, name: 'Winston'},
selectedUsers: []
}
},
components: {
child
}
}
</script>
child.vue (child)
<template>
<span>
<input type="checkbox" :value="user.name" #change="onSelectedUser">
</span>
</template>
<script>
export default {
props: ['user', 'userIdx', 'selectedUsers'],
data(){
},
methods: {
onSelectedUser() {
var idx = this.userIdx
if(event.target.checked) {
this.selectedUsers[idx] = event.target.value
}
if(event.target.checked == false) {
this.selectedUsers.splice(idx, 1)
}
}
}
}
</script>
<style scoped>
input[type="checkbox"] {
width: 20px;
height: 20px;
margin-right: 10px;
border: 3px solid red;
}
input[type="checkbox"]:checked {
width: 30px;
height: 30px;
}
</style>
Thanks
In this line of code, this.selectedUsers[idx] = event.target.value, I'm directly setting the value of an Array which vue is not able to pickup these direct modification to the array.
Excerpt from the below linked page
When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property. Similarly, Vue.js cannot pickup these changes.
More info VueJS common gotchas - why DOM isn't updating

Vue.js: Known issues with scoped styles and the v-html directive?

I'm just wondering if there is a known issue with scoped styles and the v-html directive? I seem to find that either applying the same styling to the parent or removing the scoped key work from styles everything seems to work ok...?
Example component with the issue (this works with the wordpress API if anyone with a Wordpress site want's to test with their setup):
<template>
<div v-bind:id="posts">
<div v-for="(post, i) in posts" v-bind:key="i">
<div v-html="post.content.rendered"></div>
</div>
</div>
</template>
<script>
export default {
name: "Posts",
props: {
msg: String
},
data() {
return {
posts: null
};
},
created() {
this.$http.get(this.$store.state.endpoint + "posts").then(response => {
this.posts = response.body;
});
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
p {
color: #42b983; // v-html in this example spits out numerous <p></p> tags...
}
</style>
*for replicating example, you can replace this.$store.state.endpoint with your Wordpress API endpoint, e.g., http://localhost:8000/wp-json/wp/v2 or similar.
From vue-loader docs:
Dynamically Generated Content
DOM content created with v-html are not affected by scoped styles, but you can still style them using deep selectors.
So to style the dynamic content, you should use deep selectors as seen in the following example:
<div class="posts">
<div v-for="(post, i) in posts" v-bind:key="i">
<div v-html="post.content.rendered"></div>
</div>
</div>
...
<style scoped>
.posts >>> p {
color: blue;
}
</style>
demo

Delivery data from parent to child

After googling for hours and trying all king examples I still can't figure out how to call to a function that located in a child or pass any data to a child from the parent. I tried so many examples without success and i got really confused with the Vue, is it so complicated to pass a data to a child? Here is what I came up with from one of the examples but it does not work with me, maybe I do something wrong here? Thanks :(
parent.vue
<template>
export default {
name:'parent'
data:{
retrun:{
dataTochild:''
}
},
methods:{
callToChild:function(){
this.dataTochild = 'Hello Child'
}
}
}
</template>
<child :myprop="dataTochild" />
<input #click="this.callToChild" />
child.vue
<template>
export default {
name:'child',
props:['myprop'],
watch:{
myprop:function(){
alert('Hello Parent')
}
}
}
<template>
By the way, I am using Vue 2 version!
you have a spelling error on return (retrun), and there could be other errors on code you have omitted.
Working live demo
Here it is working on a live demo
Code
Parent
<template>
<div class="parent">
<child :myprop="dataTochild" />
<button #click="callToChild">Press me!</button>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
name: "Parent",
data() {
return {
dataTochild: "",
counter: 0
};
},
methods: {
callToChild: function() {
this.counter = this.counter + 1;
this.dataTochild = "Hello Child " + this.counter;
}
},
components: {
Child
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
button {
width: 200px;
height: 60px;
}
</style>
Child
<template>
<div class="child">
<p>{{ myprop }}</p>
</div>
</template>
<script>
export default {
name: "Child",
props: ["myprop"],
watch: {
myprop: function() {
console.log("Hello Parent");
alert("Hello Parent");
}
}
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
div.child {
width: 400px;
height: 100px;
margin: auto;
}
</style>