I'm emitting an object via event bus to the parent and then assigning that object to my detail variable which is to be passed as a prop to my ContactDetail component, but the prop isn't getting the updated object.
ContactCard is a component imported in ContactList.
Getting the following error:
[Vue warn]: Property or method "contactDetail" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
ContactCard
<script>
import {bus} from "../../bus.js";
export default {
name: "ContactCard",
methods: {
showDetails: function(event) {
bus.$emit('showDetails', { pfp: this.pfp, name: this.name, address: this.address });
}
}
}
</script>
AddressBook
<template>
<div class="container">
<ContactList v-if="!showDetail"></ContactList>
<ContactDetail v-else :contactDetail="details"></ContactDetail>
</div>
</template>
<script>
import { bus } from "../bus.js";
import ContactList from "../components/addressBook/ContactList.vue";
import ContactDetail from "../components/addressBook/ContactDetail.vue";
export default {
name: "AddressBook",
components: {
ContactList,
ContactDetail
},
data() {
return {
showDetail: false,
details: {}
}
},
created() {
bus.$on('showDetails', (data) => {
console.log(data);
this.showDetail = true;
this.details = Object.assign({}, this.details, data);
console.log(this.details);
});
}
}
</script>
ContactDetail
<template>
<div class="detail-container">
{{contactDetail}}
</div>
</template>
<script>
export default {
name: "ContactDetail",
data() {``
return {
details: ''
}
},
props: {
contactDetail: {
type: Object,
required: true
}
},
watch: {
contactDetail(newVal, prevVal) {
this.details = newVal;
}
}
}
</script>
Why isn't the contactDetails prop updated in ContactDetail?
Accidentally had two script sections instead of script and style.
Before
<template>
<div class="detail-container">
{{details}}
</div>
</template>
<script>
import { bus } from "../../bus.js";
export default {
name: "ContactDetail",
data() {
return {
}
},
props: {
details: {
type: String,
required: true
}
}
}
</script>
<script>
</script>
After
<template>
<div class="detail-container">
hellow
{{details}}
</div>
</template>
<script>
import { bus } from "../../bus.js";
export default {
name: "ContactDetail",
data() {
return {
pfp: "",
name: "",
address: ""
}
},
props: {
details: {
type: String,
required: true
}
}
}
</script>
<style>
</style>
Related
I'm having trouble getting a route param to pass directly into a component. I followed multiple sets of directions in the docs (including using the Composition API as in the following code), but I'm still getting undefined when the CourseModule.vue first renders.
Route Definition
{
path: '/module/:id',
name: 'Course Module',
props: true,
component: () => import('../views/CourseModule.vue'),
},
CourseModule.vue:
<template>
<div class="AppHome">
<CustomerItem />
<CourseModuleItem :coursemodule-id="this.CoursemoduleId"/>
</div>
</template>
<script>
import { useRoute } from 'vue-router';
import CustomerItem from '../components/customers/customer-item.vue';
import CourseModuleItem from '../components/coursemodules/coursemodule-item.vue';
export default {
setup() {
const route = useRoute();
alert(`CourseModule.vue setup: ${route.params.id}`);
return {
CoursemoduleId: route.params.id,
};
},
components: {
CustomerItem,
CourseModuleItem,
},
mounted() {
alert(`CourseModule.vue mounted: ${this.CoursemoduleId}`);
},
};
</script>
coursemodule-item.vue:
<template>
<div id="module">
<div v-if="module.data">
<h2>Course: {{module.data.ModuleName}}</h2>
</div>
<div v-else-if="module.error" class="alert alert-danger">
{{module.error}}
</div>
<Loader v-else-if="module.loading" />
</div>
</template>
<script>
import Loader from '../APILoader.vue';
export default {
props: {
CoursemoduleId: String,
},
components: {
Loader,
},
computed: {
module() {
return this.$store.getters.getModuleById(this.CoursemoduleId);
},
},
mounted() {
alert(`coursemodule-item.vue: ${this.CoursemoduleId}`);
this.$store.dispatch('setModule', this.CoursemoduleId);
},
};
</script>
The output from my alerts are as follows:
CourseModule.vue setup: zzyClJDQ3QAKuQ2R52AC35k3Hc0yIgft
coursemodule-item.vue: undefined
CourseModule.vue mounted: zzyClJDQ3QAKuQ2R52AC35k3Hc0yIgft
As you can see, the path parameter works fine in the top level Vue, but not it's still not getting passed into the component.
your kebab-cased :coursemodule-id props that you're passing to the CourseModuleItem component becomes a camelCased coursemoduleId props
Prop Casing (camelCase vs kebab-case)
try this
// coursemodule-item.vue
...
props: {
coursemoduleId: String,
},
...
mounted() {
alert(`coursemodule-item.vue: ${this.coursemoduleId}`);
this.$store.dispatch('setModule', this.coursemoduleId);
},
Do you know how to change a component dynamically with object prop
App.vue
<template>
<div id="app">
<component :is="current['test'].target.name"> </component>
<input type="button" value="click me" #click="change" />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
import Comp from "./components/Comp.vue";
export default {
name: "App",
components: {
HelloWorld,
Comp,
},
data() {
return {
current: {},
};
},
created() {
this.current["test"] = {
index: 0,
target: {
name: "Comp",
},
};
},
methods: {
change() {
const r =
this.current["test"].target.name === "HelloWorld"
? "Comp"
: "HelloWorld";
this.current["test"].target = {
name: r,
};
console.log(this.current["test"]);
},
},
};
</script>
Comp.vue
<template>
<p>Template 2</p>
</template>
HelloWorld.vue
<template>
<p>Template 1</p>
</template>
https://codesandbox.io/s/clever-water-dgbts?file=/src/components/HelloWorld.vue:0-42
The value of the object will change correctly but not the component.
Thank you
The issue here is that the property test is not defined on the object current in the data definition - you're setting the definition in the created() function. This means that Vue does not know to create the reactive getter/setter for that property.
Change your data definition to:
data() {
return {
current: {
test: {
index: 0,
target: {
name: "Comp"
}
}
}
};
}
It is because of the way Vue does its reactivity (requiring pre-defined properties) that I would recommend steering clear of accessing properties as dictionary items i.e. use:
current.test.target.name
instead of
current['test'].target.name
For more information on Vue reactivity see this page: link
I learned vue's custom directive today, and start wondering if I can write a custom directive that has same function as v-model. but I find the difficulty to do the two way binding in the directive's hooks, any help?
Yes,
you should pass value props to your component and then emit input for changing value
e.g.:
We have input component:
<template>
<input :value="innerValue" #input="change($event.target.value)">
</template>
<script>
export default {
name: "TextField",
props: ["value"],
computed: {
innerValue() {
return this.value;
}
},
methods: {
change(e) {
console.log(e);
this.$emit("input", e);
}
}
};
</script>
and we use it in parent component:
<template>
<div id="app">
<text-field v-model="value"/>
</div>
</template>
<script>
import TextField from "./components/TextField";
export default {
name: "App",
components: {
TextField
},
data: () => ({
value: ""
})
};
</script>
I got "artists prop is Undefined" error in the console
Structure of components:
Discover > ArtistSlider > ArtistItem
Discover passes the prop artists to ArtistSlider and it does it only when all the data is received from API. But ArtistSlider throws the error that prop artists is undefined for some reason. At the same time, ArtistItem doesn't throw an error despite it is the child of ArtistSlider and it receives the prop artist from ArtistSlider.
I read about vue lifecycle but still cannot get why it happens. Especially in the middle of the ​component tree.
Discover:
<template>
<div>
<h1>Discover page</h1>
<artist-slider :artists="collection.new_artists"/>
</div>
</template>
<script>
import store from "#/store";
import Page from "#/mixins/Page";
import ArtistSlider from "#/components/ArtistSlider";
export default {
extends: Page,
async beforeRouteEnter(to, from, next) {
const collection = await store.dispatch("fetchUrl", {
url: "discover",
params: { location: "uae" }
});
console.log('Received collection from API', collection)
next(vm => (vm.collection = collection));
},
components: {
ArtistSlider,
},
created() {
console.log('Discover Created', this.collection)
}
};
</script>
ArtistSlider:
<template>
<section>
<h2>New Artists</h2>
<ul>
<artist-item
v-for="artist in artists"
:key="artist.slug"
:artist="artist"
/>
</ul>
</section>
</template>
<script>
import ArtistItem from './ArtistItem'
export default {
name: "AppArtistSlider",
props: {
artists: {
required: true,
type: Array
}
},
components: {
ArtistItem
},
created() {
console.log('Slider Created', this.artists)
}
};
</script>
<style lang="scss" scoped>
</style>
ArtistItem:
<template>
<li>
<app-image :src="artist.avatar.small" :alt="artist.full_name" />
<h3>{{artist.full_name}}</h3>
<p>{{artist.art_type.name}}</p>
</li>
</template>
<script>
import AppImage from './AppImage.vue'
export default {
name: 'ArtistItem',
props: {
artist: {
required: true,
type: Object
},
},
components: {
AppImage
},
created() {
console.log('Item Created', this.artist)
}
}
</script>
Will apreciate any help. Thanks!
I'm getting started with Vue, I need to create a form of tiered select fields. That is the selected option for A, uses that to call the API to get the options for B, which determines options for C.
I'm still pretty new to frontend frameworks so this might be a terrible design. However not every inclusion of A (SelectState.vue) in a view requires all the children so making them modular was my first thought.
Currently I have a top level component that displays the select options:
SelectState.vue
<template>
<div id="select-state">
<span>{{ label }}</span>
<select v-model="selectedState">
<option v-for="state in states" :key="state">
{{ state }}
</option>
</select>
</div>
</template>
<script>
export default {
name: 'select-state',
data: function () {
return {
selectedState: '',
states: ['TX']
}
},
props: ['label']
// this.states = axios.get('xxx')
}
</script>
Index.vue
<template>
<div id="form">
<v-select-state label="State"></v-select-state>
<v-select-zip label="Zip"></v-select-zip>
</div>
</template>
<script>
import SelectState from './SelectState.vue'
import SelectZip from './SelectZip.vue'
export default {
name: 'Index',
components: {
'v-select-state': SelectState,
'v-select-Zip': SelectZip
}
}
</script>
Then I have a SelectZip.vue that is identical to SelectState.vue except that it has a parameter for its axios.get('XXX', params = {'state': ???}). But I'm stuck on how to "pass" that necessary parameter.
Thanks in advance!
edit: In conjunction with #dziraf's answer my working although verbose SelectedZip.vue is as follows:
<template>
<div id="select_zip">
<span>{{ label }}</span>
<select v-model="selected_zip">
<option v-for="zip in zips" :key="zip">
{{ zip }}
</option>
</select>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'select_zip',
data: function () {
return {
zips: []
}
},
props: ['label'],
computed: {
selected_zip: {
get () { return this.$store.state.formModule.zip },
set (value) { this.$store.commit('formModule/setZips', value) }
},
selected_state: {
get () { return this.$store.state.formModule.state }
}
},
methods: {
getValidZips (state) {
axios.post('/api/v1/get_valid_zips', {
params:{'state': state }})
.then(response => {
this.zips = response.data
})
.catch(error => {
console.log(error)
})
}
},
watch: {
selected_state (value) {
this.getValidZips(value)
}
}
}
</script>
You can pass it by adding 'state' props to your select components from your main form component, but I think it isn't a good long-term solution.
Instead, consider using Vuex. An example configuration would look like this:
#/store/modules/form.js
const Form = {
namespaced: true,
state: {
state: '',
zip: ''
},
getters: {},
mutations: {
setState (state, payload) {
state.state = payload
},
setZip (state, payload) {
state.zip = payload
}
},
actions: {}
}
export default Form
#/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import Form from './modules/form'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
formModule: Form,
}
})
export default store
#/main.js
// your impots
import store from './store/index'
// your configs
new Vue({
el: '#app',
router,
store, // add store to your main Vue instance so it's accessible with this.$store
axios,
components: { App },
template: '<App/>'
});
This would be your SelectState.vue:
<template>
<div id="select-state">
<span>{{ label }}</span>
<select v-model="selectedState">
<option v-for="state in states" :key="state">
{{ state }}
</option>
</select>
</div>
</template>
<script>
export default {
name: 'select-state',
data: function () {
return {
states: ['TX']
}
},
computed: {
selectedState: {
get() { return this.$store.state.formModule.state },
set(value) { this.$store.commit('formModule/setState', value) }
}
},
props: ['label']
}
</script>
Your SelectZip.vue would be the same, except you would instead use your store's zip as your v-model.
Your store variables are accessible across your app and you can access them with computed properties in your components.