Vue JS CheckBoxGroup with a prop array as v-model - javascript

I am stuck at making a CheckBoxGroup with a prop array as v-model.
I have read the vuejs guide: https://v2.vuejs.org/v2/guide/forms.html#Checkbox which has the v-model array in the data of the same component, but it is obviously pretty useless if I want to make this component reusable and insert the v-model via props and for example check some of the boxes from "outside".
So I tried following:
CheckBoxgroup.vue
<template>
<div>
<label v-for="day in allDays" :key="day">
<input v-model="checkedDays" type="checkbox" :value="day" />
<span>{{ day }}</span>
</label>
<div>Checked days: {{ checkedDays }}</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import { Component, Prop } from 'vue-property-decorator'
#Component
export default class CheckBoxGroup extends Vue {
#Prop() checkedDays!: string[]
#Prop() allDays!: string[]
}
</script>
Index.vue
<template>
<div>
<checkbox-group :checked-days="checkedDays" :all-days="allDays" />
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import CheckboxGroup from './checkBoxGroup.vue'
#Component({
components: { CheckboxGroup },
})
export default class Index extends Vue {
// This list would usually come from an API
allDays = ['Monday', 'Tuesday', 'Wednesday']
checkedDays = ['Monday']
}
</script>
So the code is working almost fine, but I am getting
[Vue warn]: Avoid mutating a prop directly since the value will be
overwritten whenever the parent component re-renders...
Is there any way around it? Any help would be appriciated.

you can't mutate the parent state from the children directly, however you can emit the event from child to parent to mutate from there as below:
Vue.component('check-box-group', {
template: `
<div>
<label v-for="day in allDays" :key="day">
<input
v-model="checkedDays"
:value="day"
#click="$emit('update-checked-days', { newCheckedDay: day })"
type="checkbox"
/>
<span>{{ day }}</span>
</label>
<div>Checked days: {{ checkedDays }}</div>
</div>
`,
props: {
checkedDays: {
type: Array, default: () => ([])
},
allDays: {
type: Array, default: () => ([])
},
}
})
new Vue({
el: "#app",
data() {
return {
allDays: ['Monday', 'Tuesday', 'Wednesday'],
checkedDays: ['Monday']
}
},
methods: {
HandleUpdateCheckedDays({newCheckedDay}) {
const indexOfCheckedDay = this.checkedDays.findIndex(checkedDay => checkedDay === newCheckedDay)
if (indexOfCheckedDay === -1) { // if not exists then add to checkedDays
this.checkedDays.push(newCheckedDay)
} else {
this.checkedDays = this.checkedDays.filter((_, i) => i !== indexOfCheckedDay)
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js"></script>
<div id="app">
<check-box-group
:checked-days="checkedDays"
:all-days="allDays"
#update-checked-days="HandleUpdateCheckedDays"
/>
</div>
note: remember that TS class composition is deprecated.

Thanks for the answer, I actually managed to solve it also with v-model, but it looks kind of hacky and not very reusable for cases if data is injected from the outside Models.
So I will go with your solution.

Related

Vue.js passing data between components

I want to store input-value from App.vue, and use it in another component. How can I do it? I don't need the show the value in the template, I just need the value inside other components function. In JS I could just use a global var, but how can I achieve it in Vue?
App.vue:
<template>
<div id='app'>
<!-- App.vue has search bar -->
<b-form-input #keydown='search' v-model='input'></b-form-input>
<div>
<!-- Here's my other components -->
<router-view />
</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
input: '',
value: ''
}
},
methods: {
search () {
this.value = this.input
this.input = ''
}
}
}
</script>
Another component:
<template>
<div>
<p>I'm another component</p>
<p>App.vue input value was: {{value}} </p>
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
value: ''
}
}
}
</script>
This is the basic logic I'm trying to achieve. Input value in App.vue --> anotherComponent.vue
If components are not parent and child you can use store for this:
More advanced vuex store that should be your default GO TO - NPM.
Or simple solution with js object.
a. Create store.js file and export object with property in which you will store value.
b. Import store.js object to vue scripts and use it simply like:
import Store from 'store.js'
Store.value

Passing data from parent to child via props in v-for in VueJS 2

I went through similar questions but I couldn't find the answer and none of them worked for me.
Vuejs v-for Passing data Parent to Child
Vue - Passing parent data with same key name in v-for to child component
vue js how to pass data from parent, v-for loop list to child component with method
Pass data object from parent to child component
Vue.js - Pass in Multiple Props to Child in V-For
VUE / VUEX: How To Pass Data From Parent Template To Child Template
Parent.vue
<div class="col-sm-2" v-for="(item,index) in itemsData" :key="index">
<ItemWidget :item="item" />
</div>
<script>
import { mapState, mapActions } from "vuex";
import ItemWidget from "#/components/item/ItemWidget";
export default {
components: { ItemWidget },
computed: {
...mapState("item", ["itemsData"])
},
created() {
this.getItemList();
},
methods: {
...mapActions("item", ["getItemListX"]),
getItemList() {
this.getItemListX();
}
}
};
</script>
ItemWidget.vue
<template>
<div class="label">
<div class="label-value">{{ item.code }}</div>
</div>
</template>
<script>
export default {
props: ["item"]
};
</script>
itemsData is taken from the vuex by using MapState to the parent and itemsData is populated using created() method in the parent via an axios call in the vuex.
Error 1.
[Vue warn]: Error in render: "TypeError: _vm.item is undefined"
Error 2.
TypeError: "_vm.item is undefined"
How can I fix this?
Update
itemsData: [
{
code: "Test",
}
]
You should populate itemsData in computed method using ...mapState
Parent.vue
export default {
data: function () {
return {
items: this.itemsData
}
},
computed:{
...mapState('module/namespace', ['itemsData'])
}
}
<div class="col-sm-2" v-for="(item,index) in items" :key="index">
<ItemWidget :item="item" />
</div>
There is another way to declare your props:
<template>
<div class="label">
<div class="label-value">{{ item.code }}</div>
</div>
</template>
<script>
export default {
props: {
type: Object,
default: null
}
};
</script>

How to render array of component instances in vue.js?

I need to build list of dynamical components that I can group in group component. Then I need to send all information about builded components and groups.
I can use <component v-for="componentName in myComponents" :is="componentName"></component>, and get information about components using this.$children.map(component => component.getInformation()), but then I can't move some component to group component, because I have only component name not the component instance with data (it just render with default data).
I also can use this:
<template>
<div ref="container"> </div>
</template>
<script>
import someComponent from 'someComponent.vue'
import Vue from 'vue'
export default {
data () {
return {
myComponents: []
}
},
methods: {
addSomeComponent () {
let ComponentClass = Vue.extend(someComponent);
let instance = new ComponentClass({});
myComponents.push(instance);
instance.$mount();
this.$refs.container.appendChild(instance.$el)
},
getInformation () {
return this.myComponents.map(component => component.getInformation());
}
}
}
</script>
But then I can't use reactivity, directives (e.g. directives for drag and drop), and it's not data driven pattern.
Any suggestions?
<template>
<div class="component">
<template v-for="(child, index) in children()">
<component :is="child" :key="child.name"></component>
</template>
</div>
</template>
<script>
import someComponent from 'someComponent.vue'
import Vue from 'vue'
export default {
methods: {
children() {
let ComponentClass = Vue.extend(someComponent);
let instance = new ComponentClass({});
return [
instance
];
},
}
}
</script>

how to bind v-model to child component that contains input

I have some components that look like this.
<template>
<q-layout>
<v-input v-model="something" />
</q-layout>
</template>
<script>
import { QLayout } from 'quasar'
import { Input } from 'vedana'
export default {
name: 'index',
components: {
QLayout,
Input
},
data () {
return {
something: ''
}
}
}
this v-input component looks like this:
<template>
<input
:type="type ? type : 'text'"
class="v-input"/>
</template>
<script>
export default {
props: ['type'],
name: 'v-input'
}
</script>
When I enter data into the input something does not bind to whatever is in the value of the input that is inside of v-input.
How do I achieve this?
To enable the use of v-model the inner component must take a value property.
Bind the value to the inner <input> using :value, not v-model (this would mutate the prop coming from the parent). And when the inner <input> is edited, emit an input event for the parent, to update its value (input event will update the variable the parent has on v-model).
Also, if you have a default value for the type prop, declare it in props, not in the template.
Here's how your code should be
<template>
<input
:type="type"
:value="value"
#input="$emit('input', $event.target.value)"
class="v-input" />
</template>
<script>
export default {
props: {
type: {default() { return 'text'; }},
value: {} // you can also add more restrictions here
},
name: 'v-input'
}
</script>
Info about what props can have: Components / Passing data With Props.
Demo below.
Vue.component('v-input', {
template: '#v-input-template',
props: {
type: {default() { return 'text'; }},
value: {} // you can also add more restrictions here
},
name: 'v-input'
});
new Vue({
el: '#app',
data: {
something: "I'm something"
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<p>Parent something: {{ something }}</p>
<hr>
Child: <v-input v-model="something" />
</div>
<template id="v-input-template">
<input
:type="type"
:value="value"
#input="$emit('input', $event.target.value)"
class="v-input" />
</template>
https://v2.vuejs.org/v2/guide/components.html#sync-Modifier
<template>
<q-layout>
<v-input :value.sync="something" />
</q-layout>
</template>
<template>
<input
:type="type ? type : 'text'"
v-model="inputVal"
class="v-input"/>
</template>
<script>
export default {
props: ['type', 'value'],
name: 'v-input',
data:function(){
return {
inputVal: ''
};
},
watch:{
value: function(newValue){
this.$emit('update:value', newValue)
}
}
}
</script>
You need to pass your value to the input component using the .sync modifier so the changes will sync back to the parent.

vue.js passing data from parent single file component to child

Using single file architecture I'm trying to pass data (an object) from a parent component to a child:
App.vue
<template>
<div id="app">
<app-header app-content={{app_content}}></app-header>
</div>
</template>
<script>
import appHeader from './components/appHeader'
import {content} from './content/content.js'
export default {
components: {
appHeader
},
data: () => {
return {
app_content: content
}
}
}
</script>
appHeader.vue
<template>
<header id="header">
<h1>{{ app_content }}</h1>
</header>
</template>
<script>
export default {
data: () => {
return {
// nothing
}
},
props: ['app_content'],
created: () => {
console.log(app_content) // undefined
}
}
</script>
Seems to be such a trivial task and probably the solution is quite simple. Thanks for any advice :)
You're almost there.
In order to send the app_content variable from App.vue to the child component you have to pass it as an attribute in the template like so:
<app-header :app-content="app_content"></app-header>
Now, in order to get the :app-component property inside appHeader.vue you will have to rename your prop from app_component to appComponent (this is Vue's convention of passing properties).
Finally, to print it inside child's template just change to: {{ appContent }}

Categories