Multiple named components with different state - javascript

So I am trying to use multiple instances of a component inside a parent component. I am currently using a Vuetify.js tab-control to display each component.
For each child component I want to load different dynamic data from a external Rest API.
My preferd way to approach this is to pass a prop down to child's so that they can figure out what data to load. I have a vuex store for each type of child, but would like to reuse template for both, since the data is identical besides values.
Parent/Wrapper
<template>
<div>
<v-tabs fixed-tabs>
<v-tab>
PackagesType1
</v-tab>
<v-tab>
PackagesType2
</v-tab>
<v-tab-item>
<packages packageType="packageType1"/>
</v-tab-item>
<v-tab-item>
<packages packageType="packageType2"/>
</v-tab-item>
</v-tabs>
</div>
</template>
<script>
import Packages from './Packages'
export default {
components: {
Packages
}
}
</script>
Named child component
<template>
<v-container fluid>
<v-data-table v-if="packageType1"
:headers="headers"
:items="packageType1"
>
<div>
//ITEM TEMPLATE
</div
</v-data-table>
<v-data-table v-else-if="packagesType2"
:items="packagesType2"
>
<div>
//ITEM TEMPLATE
</div
</v-data-table>
</v-container>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {
name: 'packages'
}
},
props: ['packageType'],
computed: {
...mapGetters('packageModule1', ['packageType1']),
...mapGetters('packageModule2', ['packagesType2'])
},
methods: {
getPackageByType () {
if (this.packageType === 'packageType1') {
this.getPackageType1()
return
}
this.getPackageType2()
},
getPackageType1 () {
this.$store.dispatch('chocoPackagesModule/getPackageType1')
.then(_ => {
this.isLoading = false
})
},
getPackageType2 () {
this.$store.dispatch('packagesType2Module/getPackageType2')
.then(_ => {
this.isLoading = false
})
}
},
created () {
this.getPackageByType()
}
}
</script>
Right now it renders only packageType1 when loading.
How can I easily differ those types without duplicating everything?
Should I just hard differ those types? Make separate child-components and role with it that way? I am new to this, but would love as much reuse as possible.

Related

Update props in component through router-view in vue js

I want to pass a boolean value from one of my views in a router-view up to the root App.vue and then on to one of the components in the App.vue. I was able to do it but I am facing an error:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "drawer"
Below is my code:
Home.vue:
<template>
<div class="home" v-on:click="updateDrawer">
<img src="...">
</div>
</template>
<script>
export default {
name: "Home",
methods:{
updateDrawer:function(){
this.$emit('updateDrawer', true)
}
};
</script>
The above view is in the router-view and I am getting the value in the App.vue below:
<template>
<v-app class="">
<Navbar v-bind:drawer="drawer" />
<v-main class=" main-bg">
<main class="">
<router-view v-on:updateDrawer="changeDrawer($event)"></router-view>
</main>
</v-main>
</v-app>
</template>
<script>
import Navbar from '#/components/Navbar'
export default {
name: 'App',
components: {Navbar},
data() {
return {
drawer: false
}
},
methods:{
changeDrawer:function(drawz){
this.drawer = drawz;
}
},
};
</script>
I am sending the value of drawer by binding it in the navbar component.
Navbar.vue:
<template>
<nav>
<v-app-bar app fixed class="white">
<v-app-bar-nav-icon
class="black--text"
#click="drawer = !drawer"
></v-app-bar-nav-icon>
</v-app-bar>
<v-navigation-drawer
temporary
v-model="drawer"
>
...
</v-navigation-drawer>
</nav>
</template>
<script>
export default {
props:{
drawer:{
type: Boolean
}
},
};
</script>
This will work one time and then it will give me the above error. I appreciate if any one can explain what should I do and how to resolve this issue.
In Navbar.vue you are taking the property "drawer" and whenever someone clicks on "v-app-bar-nav-icon" you change drawer to be !drawer.
The problem here is that you are mutating (changing the value) of a property from the child side (Navbar.vue is the child).
That's not how Vue works, props are only used to pass data down from parent to child (App.vue is parent and Navbar.vue is child) and never the other way around.
The right approach here would be: every time, in Navbar.vue, that you want to change the value of "drawer" you should emit an event just like you do in Home.vue.
Then you can listen for that event in App.vue and change the drawer variable accordingly.
Example:
Navbar.vue
<v-app-bar-nav-icon
class="black--text"
#click="$emit('changedrawer', !drawer)"
></v-app-bar-nav-icon>
App.vue
<Navbar v-bind:drawer="drawer" #changedrawer="changeDrawer"/>
I originally missed the fact that you also used v-model="drawer" in Navbar.vue.
v-model also changes the value of drawer, which again is something we don't want.
We can solve this by splitting up the v-model into v-on:input and :value like so:
<v-navigation-drawer
temporary
:value="drawer"
#input="val => $emit('changedrawer', val)"
>
Some sort of central state management such as Vuex would also be great in this scenario ;)
What you could do, is to watch for changes on the drawer prop in Navbar.vue, like so:
<template>
<nav>
<v-app-bar app fixed class="white">
<v-app-bar-nav-icon
class="black--text"
#click="switchDrawer"
/>
</v-app-bar>
<v-navigation-drawer
temporary
v-model="navbarDrawer"
>
...
</v-navigation-drawer>
</nav>
</template>
<script>
export default {
data () {
return {
navbarDrawer: false
}
},
props: {
drawer: {
type: Boolean
}
},
watch: {
drawer (val) {
this.navbarDrawer = val
}
},
methods: {
switchDrawer () {
this.navbarDrawer = !this.navbarDrawer
}
}
}
</script>
Home.vue
<template>
<div class="home">
<v-btn #click="updateDrawer">Updrate drawer</v-btn>
</div>
</template>
<script>
export default {
name: 'Home',
data () {
return {
homeDrawer: false
}
},
methods: {
updateDrawer: function () {
this.homeDrawer = !this.homeDrawer
this.$emit('updateDrawer', this.homeDrawer)
}
}
}
</script>
App.vue
<template>
<v-app class="">
<Navbar :drawer="drawer" />
<v-main class="main-bg">
<main class="">
<router-view #updateDrawer="changeDrawer"></router-view>
</main>
</v-main>
</v-app>
</template>
<script>
import Navbar from '#/components/Navbar'
export default {
name: 'App',
components: { Navbar },
data () {
return {
drawer: false
}
},
methods: {
changeDrawer (newDrawer) {
this.drawer = newDrawer
}
}
}
</script>
This doesn't mutate the drawer prop, but does respond to changes.

List isn't dynamically displayed in vue.js

I'm new to vue.js and learning on my own with the vue doc, youtube videos and such. I've been searching for a while and looking at youtube tutorials and haven't found an answer so far, hope you guys will be able to help me.
So here's my issue, I'm building a web app and I need to display a list of objects dynamically but it doesn't show the first time I'm loading that page. I have to go to some other route and come back to see it, so I guess I'm misunderstanding some life cycle or something from that field of expertise...
I'm using the vuex to store and retrieve my data as seen below :
import Vue from 'vue';
const state = {
journees: {},
};
const getters = {
getJourneeList(state) {
return state.journees;
}
};
const mutations = {
GET_LIST(state, journees) {
state.journees = journees;
}
};
const actions = {
getJourneesUser({commit}) {
Vue.axios.get('/journee/')
.then( res => {
commit('GET_LIST', res.data)
})
.catch((err) => console.log(err))
}
};
export default {
state,
getters,
mutations,
actions
};
And then I'm getting it in my vue like this:
<template>
<v-container>
<v-card v-for="heure in heures" :key="heure._id">
<v-card-title>{{ heure }}</v-card-title>
</v-card>
</v-container>
</template>
<script>
export default {
name: "TimeList",
data() {
return {
heures: this.$store.getters.getJourneeList,
}
},
created() {
this.$store.dispatch('getJourneesUser');
}
}
</script>
You need to use mapState and use it inside computed value because then computed value will response to change in state. You do not need getter but if you want here is the version with getter. It should be like this if your store module called journees:
without getter
<template>
<v-container>
<v-card v-for="heure in journees" :key="heure._id">
<v-card-title>{{ heure }}</v-card-title>
</v-card>
</v-container>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "TimeList",
computed: {
...mapState(["journees"])
},
created() {
this.$store.dispatch("getJourneesUser");
},
};
</script>
with getter
<template>
<v-container>
<v-card v-for="heure in getJourneeList" :key="heure._id">
<v-card-title>{{ heure }}</v-card-title>
</v-card>
</v-container>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "TimeList",
computed: {
...mapGetters(["getJourneeList"])
},
created() {
this.$store.dispatch("getJourneesUser");
},
};
</script>

how to pass components as props in vue js and how to use it properly?

Hello I have a Child component that the main function is to filter and render a list of items. This child component is to be used in multiple parent components(views) and depending on the parent component the child component need to render a different child component (grand child).
Parent Component
<template>
<main>
//Child Component
<list-component
name="my items"
//List of Items I need to render
:list="items.list"
>
//Slot Passing my grandchild component
<template slot="child-component">
<component :is="child_component" :items="item"></component>
</template>
</list-component>
</main>
</template>
<script>
import ListComponent from '.ListComponent';
import ItemComponent from '.ItemComponent.vue';
export default {
components: {
ListComponent,
ItemComponent
},
data() {
return {
child_component: 'ItemComponent'
};
},
}
</script>
ListComponent.vue (child component)
<template>
<main>
<v-row class="ma-0">
<v-col v-for="(item, index) in list" :key="index" class="pa-0">
// I would like render my grandchild component here.
<slot name="child-component"></slot>
</v-col>
</v-row>
</main>
</template>
<script>
export default {
props: {
name: {
type: String,
required: true
},
list: {
type: Array,
required: true
}
}
};
</script>
ItemComponent.vue (grand child)
<template>
<div>
<v-img
src="item.image"
></v-img>
<v-row>
<span>{{
item.name
}}</span>
</v-row>
</div>
</template>
<script>
export default {
props: {
item: {
type: Object,
required: true
}
}
}
</script>
So basically I need to be able to pass ItemComponent.vue(grandchild) from the View(grandfather) to the View's ListComponent.vue(child) so that the child component can loop trough the items passed from the parent and use the grand child to render the information.
Also where do I declare the props for the grandchild?
I was able to find a solution after all I will leave this for those who need it.
basically in the child component we need to give access to the attribute to the parent trough the slot by binding the item like:
<slot name="child-component" :item="item"></slot>
and on the parent we can access it by binding the slot and giving a name to the object in this case I chose child and notice that on the component we can access item by declaring child.item
<template v-slot:child-component="child">
<component :is="child_component" :itinerary="child.item"></component>
</template>

Emitting in Vue.js and Vuetify returns error

I've got two components. One parent, and one child component. In the child component I'm using a Vuetify Button component. When a user clicks the button, it should emit from the child to the parent component.
I've tried using different Vue methods to emit and capture the emit.
These are the components I'm using. I'm working with "Single file components".
// customer.vue | Parent component
<template>
<v-container grid-list-xl fluid>
<v-layout row wrap>
<v-dialog
persistent max-width="600px">
<customer-messages #cancelmessage="handleCancelMessage"></customer-messages>
</v-dialog>
</v-layout>
</v-container>
</template>
<script>
import CustomerMessages from "#/views/customer-care/customer-care_customer-messages.vue"
export default {
components: {
CustomerMessages,
},
data() {
return {
methods: {
handleCancelMessage() {
console.log('emit worked!');
}
}
}
}
};
// customer-care_customer-messages.vue | Child component
<template>
<v-btn
color="blue darken-1"
flat
#click="cancelMessage">
Cancel
</v-btn>
</template>
<script>
export default {
data() {
return {
};
},
methods: {
cancelMessage() {
this.$emit('cancelmessage');
}
}
};
</script>
I expect the parent component to log "emit worked!". Instead, I keep getting the following error message:
[Vue warn]: Invalid handler for event "cancelmessage": got undefined
found in
---> <CustomerMessages> at src/views/customer-care/customer-care_customer-messages.vue
<ThemeProvider>
<VDialog>
<VCard>
<Customer> at src/views/customer-care/customer.vue
<VContent>
<VApp>
<App> at src/app.vue
<Root>

Flickering effect while updated - Vue Component

I've created component to search posts from API by query from URL, but if I change the query I receive strange flickering effect. Count of flicks grows by one after every change. How to solve it? Vue Devtools shows that loadPosts() is called: 1time, 2 times, 3times and so on. Where's the mistake? Reloading component gives the same effect.
<template>
<v-app>
<main-header/>
<v-layout class="mx-auto default--container">
<v-flex xs12 ma-2>
<h2 class="display-2 text-xs-center main-page--header">
<span class="text__red">W</span>yniki wyszukiwania dla:
<span class="text__red">{{this.$route.query.s}}</span>
</h2>
<article-list-sample v-for="i in articles.length" :key="`${i}`" />
</v-flex>
</v-layout>
<main-footer />
</v-app>
</template>
<script>
import MainHeader from './MainHeader';
import MainFooter from './MainFooter';
import ArticleListSample from './ArticleListSample';
import API from '../api';
export default {
components: {
ArticleListSample,
MainFooter,
MainHeader
},
name: 'search',
data: () => ({
articles: []
}),
methods: {
loadPosts() {
API.get(`posts?search=${this.$route.query.s}`)
.then(response => this.articles = response['data'])
}
},
mounted() {
this.loadPosts();
},
updated() {
this.loadPosts();
}
};
</script>
<style scoped>
</style>
To add on this, the infinite loop was created because updated was triggered whenever you were changing this.articles which would then start an other asynchronous call.
I solved it. Just need to add watcher instead of updated() method.
watch: {
'$route' () {
this.loadPosts();
}
},

Categories

Resources