Problem with watching prop changes Vue.js - vue.js

What's the problem
I wanted to assign a local component variable to prop. I constantly get Vue alert Invalid watch handler specified by key "undefined". Maybe the case is that the prop is passed from another component, where I use v-model, but I don't really know. I would really appreciate your help, because my small exercise project really depends on this mechanic.
Parent Component
Here I have some HTML select, this is where I actually model my state.selectedPhysicsModule
<template>
<div>
<div>
<h1>Some header</h1>
</div>
<form class="choosePhysicsModule">
<label for="selectedPhysicsModule"></label>
<select class="select_Module" id="selectedPhysicsModule" v-model="state.selectedPhysicsModule">
<option :value="option.value" v-for="(option, index) in importedListToSelect" :key="index">
{{option.name}}
</option>
</select>
</form>
<list_of_exercises v-if="state.selectedPhysicsModule" :what_exercises="state.selectedPhysicsModule"/>
</div>
</template>
<script>
export default {
name: 'ChoosePhysicsModule',
components: {list_of_exercises},
setup() {
const state = reactive({
selectedPhysicsModule: null,
})
return {
state,
importedListToSelect
}
}
}
Child Component
</script>
export default {
name: "list_of_exercises",
props: {
whatExercises: {
type: String,
required: true
}
},
data() {
return {
exercises: this.what_exercises,
}
},
watch: {
whatExercises: function () {
this.exercises = this.whatExercises
}
}

In the parent component where you are passing the prop you need to add a setter for the prop passed. Here is an example:
<template>
<div id="app">
<label>
<input name="whatExercises" v-model="whatExercises">
</label>
<ListOfExercises v-if="whatExercises" :what_exercises="whatExercises" />
</div>
</template>
<script>
export default {
data() {
return {
whatExercises: null,
}
}
}
</script>
P.S: as a side note, I recommend using camelCase for prop names. It's more in-line with the rest of the community. If you have time feel free to check out the style guide on the official website.

Related

How to pass class attribute to child component element

how can I pass class attribute from parent to child component element?
Look here:
https://codesandbox.io/s/pedantic-shamir-1wuuv
I'm adding class to the Component "Input Field"
And my goal is that the 'custom-class' will be implemented on the 'input' element in the child component
But just still using the class attribute, and not setting a new prop like "customClass" and accept it in the props of the component
Thanks!
This depends on the template structure of your ChildComponent
Your Parent Component can look like this:
<div id="app">
<InputField class="anyClass" />
</div>
If your Child looks like this:
<template>
<input ... />
</template
Because if you have only 1 root Element in your template this will inherit the classes given automatically.
if your Template e.g. looks like this: (only available in vue3)
<template>
<input v-bind="$attrs" />
<span> hi </span>
</template
You will need the v-bind="$attrs" so Vue knows where to put the attributes to. Another Solution would be giving classes as props and assigning it to the element with :class="classes"
The pattern for the customs form component in Vue 2 where the props go to a child element looks something like this.
<template>
<div class="input-field">
<label v-if="label">{{ label }}</label>
<input
:value="value"
:class="inputClass"
#input="updateValue"
v-bind="$attrs"
v-on="listeners"
/>
</div>
</template>
<script>
export default {
inheritAttrs: false,
props: {
label: {
type: String,
default: "",
},
value: [String, Number],
inputClass: {
type: String,
default: "",
},
},
computed: {
listeners() {
return {
...this.$listeners,
input: this.updateValue,
};
},
},
methods: {
updateValue(event) {
this.$emit("input", event.target.value);
},
},
};
</script>
The usage of those components could look something like this.
```html
<template>
<div id="app">
<InputField
v-model="name"
label="What is your name?"
type="text"
class="custom"
inputClass="input-custom"
/>
<p>{{ name }}</p>
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
data() {
return {
name: "",
};
},
};
</script>
A demo is available here
https://codesandbox.io/s/vue-2-custom-input-field-4vldv?file=/src/components/InputField.vue
You need to use vue props . Like this:
child component:
<template>
<div>
<input :class="className" type="text" value="Test" v-bind="$attrs" />
</div>
</template>
<script>
export default {
props:{
className:{
type:String,
default:'',
}
}
};
</script>
Parent component:
<div id="app">
<InputField :class-name="'custom-class'" />
</div>
Since you're using vue2 the v-bind=$attrs is being hooked to the root element of your component the <div> tag. Check the docs. You can put this wrapper on the parent element if you need it or just get rid of it.
Here is a working example
Another approach
There is also the idea of taking the classes from the parent and passing it to the child component with a ref after the component is mounted
Parent Element:
<template>
<div id="app">
<InputField class="custom-class" ref="inputField" />
</div>
</template>
<script>
import InputField from "./components/InputField";
export default {
name: "App",
components: {
InputField,
},
mounted() {
const inputField = this.$refs.inputField;
const classes = inputField.$el.getAttribute("class");
inputField.setClasses(classes);
},
};
</script>
Child Element:
<template>
<div>
<input type="text" value="Test" :class="classes" />
</div>
</template>
<script>
export default {
data() {
return {
classes: "",
};
},
methods: {
setClasses: function (classes) {
this.classes = classes;
},
},
};
</script>
Here a working example
In Vue2, the child's root element receives the classes.
If you need to pass them to a specific element of your child component, you can read those classes from the root and set them to a specific element.
Example of a vue child component:
<template>
<div ref="root">
<img ref="img" v-bind="$attrs" v-on="$listeners" :class="classes"/>
</div>
</template>
export default {
data() {
return {
classes: null,
}
},
mounted() {
this.classes = this.$refs.root.getAttribute("class")
this.$refs.root.removeAttribute("class")
},
}
Pretty much it.

How can I emit from component and listen from another one?

I have in Layout.vue to components one TheSidebar second TheHeader, there is a button in TheHeader to open the sidebar in TheSidebarcomponent.
I need to when I click the button in header open the sidebar:
My try:
in TheHeader:
methods: {
openSidebar() {
this.$root.$emit("open-sidebar");
},
},
in TheSidebar
data() {
return {
sidebarOpen: false,
};
},
mounted() {
this.$root.$on("open-sidebar", (this.sidebarOpen = true));
},
I'm using VUE 3 so I got this error in console: TypeError: this.$root.$on is not a function so How can communicate ?
you can use something like tiny emitter it works fine and doesn't care about parent child relationship
var emitter = require('tiny-emitter/instance');
emitter.on('open-sidebar', ({isOpen}) => {
//
});
emitter.emit('open-sidebar', {isOpen : true} );
You can only pass props to a direct child component, and
you can only emit an event to a direct parent. But
you can provide and eject from anywhere to anywhere
Per another answer, provide and eject may be your best bet in Vue 3, but I created a simple example of how to implement with props/events. Built with Vue 2 as I haven't worked with 3 yet, but should be usable in Vue 3 as well.
Parent.vue
<template>
<div class="parent">
<div class="row">
<div class="col-md-6">
<h4>Parent</h4>
<hr>
<child-one #show-child-two-event="handleShowChildTwoEvent" />
<hr>
<child-two v-if="showChildTwo" />
</div>
</div>
</div>
</template>
<script>
import ChildOne from './ChildOne.vue'
import ChildTwo from './ChildTwo.vue'
export default {
components: {
ChildOne,
ChildTwo
},
data() {
return {
showChildTwo: false
}
},
methods: {
handleShowChildTwoEvent() {
this.showChildTwo = true;
}
}
}
</script>
ChildOne.vue
<template>
<div class="child-one">
<h4>Child One</h4>
<button class="btn btn-secondary" #click="showChildTwo">Show Child Two</button>
</div>
</template>
<script>
export default {
methods: {
showChildTwo() {
this.$emit('show-child-two-event');
}
}
}
</script>
ChildTwo.vue
<template>
<div class="child-two">
<h4>Child Two</h4>
</div>
</template>

Can't pass checked value to parent component from custom checkbox component

In my custom checkbox component, I'm trying to pass the value of the checkbox form field to my parent component:
<template>
<div class="custom-checkbox">
<div :class="{ 'bg-white': value }">
<input
:id="checkboxId"
type="checkbox"
:checked="value"
v-model="value"
#change="$emit('input', $event.target.checked)"
/>
</div>
<label :for="checkboxId">
<slot />
</label>
</div>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
default: false
},
checkboxId: {
type: String,
default: "checkbox-1"
}
}
};
</script>
Getting this error:
[Vue warn]: 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: "value"
I tried to add:
data() {
return {
checkedValue: this.value
};
}
... then replace v-model="value" with v-model="checkedValue" but the checkbox doesn't check anymore and I still don't get the value of it in parent component.
Any suggestion?
It's because you are still directly mutating value, it does not matter if you catch the #change event or not.
Try creating a computed component with a getter/setter in your child component.
computed: {
checked: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value);
}
}
}
Use checked as your checkbox v-model. No need to bind anything to :checked, only v-model will suffice.
You can pass the value using v-model to this component in the parent.
For reference: https://v2.vuejs.org/v2/guide/forms.html
For the record.
CustomCheckbox.vue
<template>
<input type="checkbox" :checked="value" #change="$emit('input', $event.target.checked)">
</template>
<script>
export default {
props: ["value"]
};
</script>
Parent.vue
<template>
<div id="app">
<custom-checkbox v-model="checked"></custom-checkbox>
</div>
</template>
<script>
import CustomCheckbox from "./components/CustomCheckbox";
export default {
data: {
checked: true
},
components: {
CustomCheckbox
}
};
</script>

Only show slot if it has content, when slot has no name?

As answered here, we can check if a slot has content or not. But I am using a slot which has no name:
<template>
<div id="map" v-if="!isValueNull">
<div id="map-key">{{ name }}</div>
<div id="map-value">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
props: {
name: {type: String, default: null}
},
computed: {
isValueNull() {
console.log(this.$slots)
return false;
}
}
}
</script>
I am using like this:
<my-map name="someName">{{someValue}}</my-map>
How can I not show the component when it has no value?
All slots have a name. If you don't give it a name explicitly then it'll be called default.
So you can check for $slots.default.
A word of caution though. $slots is not reactive, so when it changes it won't invalidate any computed properties that use it. However, it will trigger a re-rendering of the component, so if you use it directly in the template or via a method it should work fine.
Here's an example to illustrate that the caching of computed properties is not invalidated when the slot's contents change.
const child = {
template: `
<div>
<div>computedHasSlotContent: {{ computedHasSlotContent }}</div>
<div>methodHasSlotContent: {{ methodHasSlotContent() }}</div>
<slot></slot>
</div>
`,
computed: {
computedHasSlotContent () {
return !!this.$slots.default
}
},
methods: {
methodHasSlotContent () {
return !!this.$slots.default
}
}
}
new Vue({
components: {
child
},
el: '#app',
data () {
return {
show: true
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button #click="show = !show">Toggle</button>
<child>
<p v-if="show">Child text</p>
</child>
</div>
Why you dont pass that value as prop to map component.
<my-map :someValue="someValue" name="someName">{{someValue}}</my-map>
and in my-map add prop:
props: {
someValue:{default: null},
},
So now you just check if someValue is null:
<div id="map" v-if="!someValue">
...
</div

Vue Multiselect: How to send console.log once selection has been made

Using Vue Multiselect, I am trying to send a console.log once I have made a selection. I thought it would work by putting it in the watch but it does not work. Where should it be placed. Please see my component below.
Component
<template>
<div>
<label v-for="topic in topics" class="radio-inline radio-thumbnail" style="background-image: url('http://s3.hubsrv.com/trendsideas.com/profiles/74046767539/photo/3941785781469144249_690x460.jpg')">
<input type="radio" v-model="internalValue" name="topics_radio" :id="topic.id" :value="topic.name">
<span class="white-color lg-text font-regular text-center text-capitalize">{{ topic.name }}</span>
</label>
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
internalValue: this.value,
topics: []
}
},
mounted(){
axios.get('/vuetopics').then(response => this.topics = response.data);
},
watch: {
internalValue(v){
this.$emit('input', v);
console.log('topic has been chosen!!!');
}
}
}
</script>
It fires events, so you may catch them.
<multiselect ... #select="doSomething" ...>
Then add your method
...
methods: {
doSomething(selectedOption, id) {
console.log(selectedOption);
}
}
Make sure you implemented vue-multiselect correctly, I don't see the component in your code.