Dynamic `v-model` for computed properties - based on route param - javascript

I am building a component that can be used to set various vuex properties, depending on the name passed in the route. Here is the naive gist of it:
<template>
<div>
<input v-model="this[$route.params.name]"/>
</div>
</template>
<script>
export default {
computed: {
foo: {
get(){ return this.$store.state.foo; },
set(value){ this.$store.commit('updateValue', {name:'foo', value}); }
},
bar: {
get(){ return this.$store.state.bar; },
set(value){ this.$store.commit('updateValue', {name:'bar', value}); }
},
}
}
</script>
Note that I pass this[$route.params.name] to the v-model, to make it dynamic. This works for setting (component loads fine), but when trying to set a value, I get this error:
Cannot set reactive property on undefined, null, or primitive value: null
I assume this is because this inside v-model becomes undefined (?)
How can I make this work?
UPDATE
I would also be curious to know why this does not work (compilation error):
<template>
<div>
<input v-model="getComputed()"/>
</div>
</template>
<script>
export default {
computed: {
foo: {
get(){ return this.$store.state.foo; },
set(value){ this.$store.commit('updateValue', {name:'foo', value}); }
},
bar: {
get(){ return this.$store.state.bar; },
set(value){ this.$store.commit('updateValue', {name:'bar', value}); }
},
},
methods: {
getComputed(){
return this[this.$route.params.name]
}
}
}
</script>

Yeah everything inside your <template> is in the this scope, so this is undefined.
v-model is just a syntactic sugar for :value and #input, so you can handle this with a custom event and a computed property for :value.
You can also use a computed property with a getter and setter; Something like
computed: {
model: {
get: function () {
return this.$store.state[this.$route.params.name]
},
set: function (value) {
this.$store.commit('updateValue', { name: this.$route.params.name, value})
}
}
}
Edit
If you have more logic to do in your setter, i'd separate it like so, keep the getter simple, and stick to one computed property;
computed: {
model: {
get: function () {
return this.$store.state[this.$route.params.name]
},
set: function (value) {
switch(this.$route.params.name) {
case 'foo':
return this.foo(value)
default:
return this.bar(value)
}
}
}
},
methods: {
foo(val) {
this.$store.commit(...)
},
bar(val) {
this.$store.commit(...)
}
}

Related

vuejs filter function shows an empty array

I create a filter function and I want to show the result. Here's the code
data() {
return {
questionList: faqData.flatMap(q => q.questions)
}
},
computed: {
search() {
return this.$store.state.search
},
filter: function() {
this.questionList.filter((x) => {
return x.question.match(this.search);
})
}
}
There's no problem with the questionList variable and the search() function. And I think the problem is in the filter() function. Anyway, here's my questionList
[{id: 1, question: 'blabla', answer: 'blabalbla'}, {id: 2, question: 'blabla', answer: 'blabalbla'}, {id: 3, question: 'blabla', answer: 'blabalbla'} ]
the filter function shows an empty array. Can anyone tell me where did I do it wrong? Thanks!
Computed properties' values are defined by what their functions return.
In your case undefined since filter: function() { does not return anything.
It is not a good idea at all to assign a state variable inside a computed property function!
The correct way to do it is:
filter: function() {
return this.questionList.filter((question) => {
return question.title.match(this.search);
})
}
<template>
<div class="hello">
<div>
<p v-for="(item, index) in filt" :key="index">
{{item.question}}
</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
questionList: faqData.flatMap(q => q.questions)
}
},
computed: {
search() {
return this.$store.state.search;
},
filt: function() {
return this.questionList.filter((x) => {
return x.question.match(this.search);
})
}
}
}
</script>

Computed property was assigned to but it has no setter - a toggle component

I am creating a simple switch toggle component in Vue where it has a v-model and #updated. But I can't seem to change the model when the user toggles the switch. First I was getting the error to avoid mutating a prop directly. But now I am getting another error.
[Vue warn]: Computed property "isSwitchOn" was assigned to but it has
no setter.
The component is meant to be used like this
<iswitch v-model="switchGender" #updated="handleUpdatedGender" />
Here is the component itself
export default {
template: `
<span
#click="toggleSwitch"
:class="{ active: isSwitchOn }">
<span class="toggle-knob"></span>
</span>
`,
props: ['value'],
methods:
{
toggleSwitch()
{
this.isSwitchOn = !this.isSwitchOn
this.$emit('input', this.isSwitchOn)
this.$emit('updated')
}
},
computed:
{
isSwitchOn()
{
return this.value
}
},
};
The error is triggered by this statement: this.isSwitchOn = !this.isSwitchOn. You are trying to assign a value to a computed property but you didn't provide a setter.
You need to define your computed property as follow for it to work as a getter and a setter:
computed:
{
isSwitchOn:
{
get()
{
return this.value
},
set(value)
{
this.value = value
}
}
}
Also, it is not advised to mutate a prop directly. What you could do is to add a new data property and sync it with the value prop using a watcher.
I think something like this will work:
props: ['value'],
data()
{
return {
val: null
}
},
computed:
{
isSwitchOn:
{
get()
{
return this.val
},
set(value)
{
this.val = value
}
}
},
watch: {
value(newVal) {
this.val = newVal
}
}
Computed properties are by default getter-only, but you can also provide a setter when you need it. Check official documentation
computed:
{
isSwitchOn() {
get() { return this.value }
set(val) { this.value = val }
}
}
Alternative way:
In your parent component:
<iswitch ref="switcher" #input="methodForInput" v-model="switchGender" #updated="handleUpdatedGender" />
methods: {
methodForInput(event){
this.$refs.switcher.isSwitchOn = event;
}
}
In your child component:
export default {
template: `
<span
#click="toggleSwitch"
:class="{ active: isSwitchOn }">
<span class="toggle-knob"></span>
</span>
`,
data() {
return {
isSwitchOn: false
};
},
methods:
{
toggleSwitch()
{
this.isSwitchOn = !this.isSwitchOn
this.$emit('input', this.isSwitchOn)
this.$emit('updated')
}
}
};
Updates 3: Sorry, didn't include parent component at first.

Why `#change` trigger don't work for v-data-picker?

I use v-calendar package in my Vue.js application.
I want to send selected data range values to parent component. Why #change trigger don't work?
Parent.vue:
<template>
<div>
<Child #setRange="setRange" :range="range"/>
</div>
</template>
<script>
data() {
return {
range: this.range,
}
},
mounted() {
firstCallToPage();
},
methods: {
firstCallToPage(){
axios.get('URL').then(response => {
let self = this;
this.range = {
start: response.startDate,
end: response.endDate,
};
}
},
setRange(range_value) {
this.range = range_value;
}
}
</script>
Child.vue:
<v-date-picker class='v-date-picker'
mode='range'
v-model='rangeValue'
:show-day-popover=false
:max-date='new Date()'
show-caps
:input-props='{placeholder: "", readonly: true}'
#change="sendRange">
</v-date-picker>
props: {
range: {
type: Object,
},
},
data() {
return {
rangeValue: this.range
}
},
sendRange: function () {
this.$emit('setRange', this.rangeValue);
}
ERROR in console:
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: "range"
Try #input instead of #change. In v-datetime-picker works only with #input.
The error message is pretty explicit. The problem is that you give a prop to your child component (the one that contains v-date-picker) and you are overriding this prop with v-model (v-model is just syntactic sugar for :value and #change).
Derive your prop's value with a data value and use it for your operations:
<v-date-picker class='v-date-picker'
mode='range'
v-model='rangeValue'
:show-day-popover=false
:max-date='new Date()'
show-caps
:input-props='{placeholder: "", readonly: true}'
>
</v-date-picker>
props: {
range: {
type: Object,
},
},
data() {
return {
rangeValue: this.range
}
},
sendRange: function () {
this.$emit('setRange', this.rangeValue);
}
Instead of using a method, you can make use of watch...
Consider you have the following attributes in the range Object
range: {
start:value,
end: value
}
<v-date-picker class='v-date-picker'
mode='range'
v-model='rangeValue'
:show-day-popover=false
:max-date='new Date()'
show-caps
:input-props='{placeholder: "", readonly: true}'
>
</v-date-picker>
props: {
range: {
type: Object,
},
},
watch: {
'rangeValue.start': function(newVal){
this.$emit('setRange', newVal);
}
},
data() {
return {
rangeValue: this.range
}
}

Vue2: handling multi-child prop synchronization with models

I'm new to Vue (about a, and while reading the docs is very helpful it is often the case where I can not derive how I am supposed to achieve the desired behavior.
I have made several small components to get the feel for passing props and handling events, so now I am trying to makes something a bit larger but am facing some difficulty.
This difficulty stems from the following:
I would like to have a custom select component that are initialized via a v-for loop. All the while I would like to have access to these components selected option. I can bind the select data with v-model in the select component, but I am struggling to get that information out to the wrapper, yet alone the container looping over the wrappers.
Note: I am using Rollup and single file components
top-level
<template >
<div>
<select-container
v-for="(select, index) in selects"
:index="index"
:key="select.id"
:select.sync="select"
/>
</div>
</template>
<script>
import selectContainer from './select-container.vue';
export default {
components: { selectContainer },
props: {
records: {
default: function(){return{}},
type: Object
}
},
data: function() {
return {
selects: [{}, {}]
}
},
computed: {
},
methods: {
}
}
</script>
<style scoped>
</style>
select-container
<template>
<div>
<my-select
v-model.sync="select"
/>
</div>
</template>
<script>
import mySelect from './my-select.vue';
export default {
components: { mySelect },
props: {
index: { type: Number },
select: {
type: Object,
default: function(){return{}}
}
},
data : function(){
return {
}
},
methods: {
},
computed: {
}
}
</script>
<style scoped>
</style>
my-select
<template>
<select v-model="selected">
<option
v-for="(attr, index) in attributes"
:value="attr"
:selected="attr == selected"
>
{{attr}}
</option>
</select>
</template>
<script>
export default {
props: {
attributes: {
type: Array,
default: function() {
return []
}
}
},
data: function() {
return {
selected: ""
}
}
}
</script>
<style scoped>
</style>
There's still a lot of code in the post that I'm not following, but I think the gist of your question is how to reflect the selected property from the <my-select> component through to the <select-container> component. If that's the case, then the most straightforward approach is probably just to add a value and emit input events.
In the template, add an event handler for the native <select>
<select v-model="selected" #input="onInput">
Then, in the code, reflect that event up to the parent. Also be sure to accept a value property from that parent.
export default {
props: {
value: null,
},
data: function() {
return {
selected: this.value
}
},
methods: {
onInput() {
this.$emit("input", this.selected)
}
},
watch: {
value(newValue) {
this.selected = newValue;
}
}
}
And then the parent can simply use the conventional v-model binding.
<my-select v-model="select"/>

Passing props dynamically to dynamic component in VueJS

I've a dynamic view:
<div id="myview">
<div :is="currentComponent"></div>
</div>
with an associated Vue instance:
new Vue ({
data: function () {
return {
currentComponent: 'myComponent',
}
},
}).$mount('#myview');
This allows me to change my component dynamically.
In my case, I have three different components: myComponent, myComponent1, and myComponent2. And I switch between them like this:
Vue.component('myComponent', {
template: "<button #click=\"$parent.currentComponent = 'myComponent1'\"></button>"
}
Now, I'd like to pass props to myComponent1.
How can I pass these props when I change the component type to myComponent1?
To pass props dynamically, you can add the v-bind directive to your dynamic component and pass an object containing your prop names and values:
So your dynamic component would look like this:
<component :is="currentComponent" v-bind="currentProperties"></component>
And in your Vue instance, currentProperties can change based on the current component:
data: function () {
return {
currentComponent: 'myComponent',
}
},
computed: {
currentProperties: function() {
if (this.currentComponent === 'myComponent') {
return { foo: 'bar' }
}
}
}
So now, when the currentComponent is myComponent, it will have a foo property equal to 'bar'. And when it isn't, no properties will be passed.
You can also do without computed property and inline the object.
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
Shown in the docs on V-Bind - https://v2.vuejs.org/v2/api/#v-bind
You could build it like...
comp: { component: 'ComponentName', props: { square: true, outlined: true, dense: true }, model: 'form.bar' }
<component :is="comp.component" v-bind="{...comp.props}" v-model="comp.model"/>
I have the same challenge, fixed by the following:
<component :is="currentComponent" v-bind="resetProps">
{{ title }}
</component>
and the script is
export default {
…
props:['title'],
data() {
return {
currentComponent: 'component-name',
}
},
computed: {
resetProps() {
return { ...this.$attrs };
},
}
<div
:color="'error'"
:onClick="handleOnclick"
:title="'Title'"
/>
I'm came from reactjs and I found this solve my issue
If you have imported you code through require
var patientDetailsEdit = require('../patient-profile/patient-profile-personal-details-edit')
and initalize the data instance as below
data: function () {
return {
currentView: patientDetailsEdit,
}
you can also reference the component through the name property if you r component has it assigned
currentProperties: function() {
if (this.currentView.name === 'Personal-Details-Edit') {
return { mode: 'create' }
}
}
When you use the <component> inside a v-for you can change the answer of thanksd as follow:
methods: {
getCurrentProperties(component) {
if (component === 'myComponent') {
return {foo: baz};
}
}
},
usage
<div v-for="object in object.items" :key="object._your_id">
<component :is="object.component" v-bind="getCurrentProperties(object.component)" />
</div>
Let me know if there is an easier way.

Categories

Resources