Passing value to Child Component is updating only once - vue.js

In my Vue application, I have a list of months in my parent component like this-
When clicking on any month, I would like to pass its id to the child component named timeComponent. Below is the code.
Parent Component-
<template>
<div class="content container-fluid" id="prayer_time">
<div class="col-md-12">
<div class="card">
<div class="card-header p-2">
<ul class="nav nav-pills">
<li class="nav-item" v-for="(month, index) in months" :key="index">
<a class="nav-link" v-on:click="monthId(index + 1)">
{{ month }}
</a>
</li>
</ul>
</div><!-- /.card-header -->
</div>
<!-- /.card -->
</div>
<div v-if="prayertimeshow">
<timeComponent :month_id=month_id :mosque_id=this.mosque_id />
</div>
</div>
</template>
<script>
import timeComponent from './TimeComponent.vue';
export default {
props: ['mosque_id'],
components: {
timeComponent
},
data(){
return {
month_id: '',
prayertimeshow: '',
months : ['January', 'February', 'March','April','May','June','July','August','September','October','November','December']
}
},
methods:{
monthId(event) {
this.month_id = event;
this.prayertimeshow = true;
}
}
}
</script>
The problem is that when I click on any month for the first time, the month_id value is passed to the child component perfectly.
But it is not working when I click on another month for the second time.
In the child component, I am accessing the prop value like this-
<script>
export default {
props: ['month_id'],
created: function () {
console.log(this.month_id);
}
}
</script>
Is it the correct way to do this?

The issue is that created hook runs only once when the child component has been created.
The better approach to check the updated prop value in the child is to use a watch hook.
You can check the update in month_id like this-
Child Component-
<template>
<div>{{ month_id }}</div>
</template>
<script>
export default {
props: ["month_id"],
watch: {
month_id(newVal, oldVal) {
// newVal = updated month_id
this.getTime(newVal);
},
},
methods: {
getTime(input) {
console.log(input);
},
},
};
</script>

Related

VUE3 use different v-for for the same component

I have a JobComponent.vue component where I fetch data from a VUEX Store. This component is used on two separate pages, first page Home.vue and second page AllJobs.vue.
In AllJobs.vue I used JobComponent.vue and everything is works fine, it's rendering all the jobs, but, here comes the problem...
In Home.vue I want to render only the last 5 jobs, so in store I make a getter that slice me only the latest 5 jobs.
How can I use this latestJobs from getters on the same component?
When I import the component in Home.vue page I can't use another v-for direct on the component...
here you can see my project structure and files
Home.vue
<template>
<div class="cards-container">
<JobComponent />
</div>
</template>
JobComponent.vue
<template>
<div v-for="job in allJobs" :key="job.id" class="card">
<div class="position">{{ job.position }}</div>
<div class="department">{{ job.department }}</div>
<div class="location">
<span class="material-symbols-outlined">location_on</span>
{{ job.location }}
</div>
<span class="material-symbols-outlined right-arrow">arrow_right_alt</span>
<span #click="deleteJob(job.id)" class="material-symbols-outlined right-arrow">delete</span>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
methods: {
...mapActions(['fetchJobs', 'deleteJob']),
},
computed: mapGetters(['allJobs']),
created() {
this.fetchJobs();
}
}
</script>
store.js (vuex)
const getters = {
allJobs: (state) => state.jobs,
latestJobs: (state) => {
const response = state.jobs.slice(0, 5);
return response;
}
};
Your component should be as independent as possible from the store. It's role is to display what ever is provided so it could be reused as you want, using props :
JobComponent.vue
<template>
<div class="card">
<div class="position">{{ position }}</div>
<div class="department">{{ department }}</div>
<div class="location">
<span class="material-symbols-outlined">location_on</span>
{{ location }}
</div>
<span class="material-symbols-outlined right-arrow">arrow_right_alt</span>
<span #click="$emit('deleteJob', id)" class="material-symbols-outlined right-arrow">delete</span>
</div>
</template>
<script>
export default {
props: {
id: string,
position: string,
department: string,
location: string
}
}
</script>
In this component you only display the provided data, and leave the responsibility of the parent component to choose how many components to display.
Home.vue
<template>
<div class="cards-container">
<JobComponent v-for="job in jobs" :key="job.id" :id="job.id" :position="job.position" :department="job.department" :location="job.location" #delete-job="deleteJob" />
</div>
</template>
<script>
export default {
created() {
this.$store.dispatch('fetchJobs')
},
computed: {
jobs() {
return this.$store.getters['latestJobs'] // Or allJobs, just make sure your getter returns an array even if no jobs are loaded yet.
}
},
methods: {
deleteJob() {
// Your logic for job delete
}
}
}
</script>

Vue : params value don't change in created method

I have this link to another component with params :
<div class="dropdown-menu bg-light" aria-labelledby="navbarDropdown">
<div v-for="category in categories" :key="category.id">
<div v-if="lang=='ku'"><li><a class="dropdown-item font-weight-bold py-2" href="#" ><router-link :to="{name:'category',params:{id:category.id}}">{{category.catagory_ku}}</router-link></a></li></div>
<div v-else><li><a class="dropdown-item font-weight-bold py-2" href="#" ><router-link :to="{name:'category',params:{id:category.id}}">{{category.catagory_ar}}</router-link></a></li></div>
</div>
</div>
the category component like this :
<template>
<div style="margin-top:132px">
Categories {{id}}
</div>
</template>
<script>
export default {
data(){
return {
id :''
}
},
created(){
this.id=this.$route.params.id
}
}
</script>
when i pressed the route link the id value changed in url but in the page view it just take first id value I clicked and not be changed after I clicked another same link by different id value
Try to define the id as computed property :
<template>
<div style="margin-top:132px">
Categories {{id}}
</div>
</template>
<script>
export default {
computed:{
id(){
return this.$route.params.id
}
}
}
</script>
this should be in the mounted hook
created(){
this.id=this.$route.params.id
}
// replace with
mounted(){
this.id = this.$route.params.id
}
also if you wanna perform some extra actions related to the id changes, you can watch the route
<template>
<div style="margin-top:132px">
Categories {{ id }}
</div>
</template>
<script>
export default {
data() {
return {
id: ''
}
},
mounted() {
this.id = this.$route.params.id
},
watch: {
$route(to) {
this.id = to.params.id
// do other logics here
}
}
</script>

Is it possible to call a function in one vue component from another vue component?

LogoutModal.vue
<template>
<div class="modal LogoutModal" v-bind:class="{'is-active':confirmLogout}">
<div class="modal-background"></div>
<div class="modal-content">
</div>
<button class="modal-close is-large" aria-label="close" #click="closeModal"></button>
</div>
</template>
<script>
export default {
name: "LogoutModal",
data() {
return {
confirmLogout: false
};
},
methods: {
closeModal: function() {
this.confirmLogout = false;
},
showModal: function() {
this.confirmLogout = true;
}
}
};
</script>
Navigation.vue
<template>
<aside class="menu">
<ul class="menu-list">
<li>
<a #click="showModal">Logout</a>
</li>
</ul>
<LogoutModal />
</aside>
</template>
<script>
import LogoutModal from "#/components/LogoutModal.vue";
export default {
name: "Navigation",
components: {
LogoutModal
}
};
</script>
I want to call the showModal function when i click on the Logout link. How can i achive that?
Or is that possible to change the variable in LogoutModal.vue from Navigation.vue. I have a variable called confirmLogout in LogoutModal.vue it has to be updated from Navigation.vue. How can I do that?
You should have confirmLogout in the navigation component, then pass that into your modal, you will also need a way to close the modal so in your modal you should this.$emit('close') when your user has signaled they want to close it.
<LogoutModal :open="confirmLogout" #close="confirmLogout=false" />
In your logout modal add a prop
<script>
export default {
name: "LogoutModal",
props: ['open'],
...
then in the template
<div class="modal LogoutModal" v-bind:class="{'is-active':open}">
You could define a dynamic property in LogoutModal, that sets it's state.
Here's more about that: https://v2.vuejs.org/v2/guide/components-props.html

Vue Eventbus: handler.apply is not a function

I want to reload one of my components when an other component changes (for example I send a put with axios)
I tried it with eventbus but I got this error:
handler.apply is not a function
In the component where I want to trigger:
EventBus.$emit('compose-reload', Math.random()*100);
Where I want to be triggered:
<template>
<div class="">
<div class="">
<div class="columns">
<div class="column is-one-fifth">
<div class="box">
<Menu></Menu>
</div>
</div>
<div class="column is-four-fifth">
<div class="box">
<router-view :key="key"></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Menu from './includes/Menu'
import EventBus from '../../../event-bus';
export default {
components: {
Menu,
},
data() {
return {
key: null
}
},
mounted(){
EventBus.$on('compose-reload', this.key);
},
created(){
this.key = Math.random()*100
}
}
</script>
EventBus.$on expects a handler function as a second argument but the variable this.key is passed in, hence the error.
You should change this :
mounted(){
EventBus.$on('compose-reload', this.key);
}
To this :
mounted(){
EventBus.$on('compose-reload', key => {
this.key = key;
});
}

Conditional rendering template on Vue?

I have a simple situation that I want to render cart-badge based on window width value but it always show the last one even though windowWidth does change the value. Please advise me what to do !
<template v-if="windowWidth>=1024">
<div class="nav-user-account"> {{windowWidth}}
<div class="nav-cart nav-cart-box">
<span class="text hidden-sm">Cart</span>
<span class="cart-number" id="nav-cart-num">{{cartItemCount}}</span>
</div>
</div>
</template>
<template v-if="windowWidth<1024">
<a href="#" style="color:#ff6a00" class="icon-cart">
{{windowWidth}}
<i class="fa fa-shopping-cart fa-2x" aria-hidden="true"></i>
<span class="cart-num">2</span>
</a>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data() {
return {
windowWidth: window.innerWidth,
}
},
computed: {
...mapGetters('cart', {
cartItemCount: 'getShoppingCartItemsCount'
})
},
mounted() {
this.$nextTick(() => {
window.addEventListener('resize', () => {
this.windowWidth= window.innerWidth
});
})
},
}
</script>
UPDATED: as Tony19 pointed out, i need a master template outside of these 2 template.
<template>
<div>
<template v-if="windowWidth>=1024">...</template>
<template v-else></template>
</div>
</template>