Can't pass value prop to custom input component - vue.js

I have a custom input component that generally works well. But since yesterday I want to pass to it a value from the parent component in certain cases (namely if a cookie is found for that field, pre-fill the field with the cookie value).
Parent component (simplified):
<custom-input
v-model="userEmail"
value="John Doe"
/>
But for a reason I cannot comprehend, the value prop doesn't work. Why not?
My custom input component (simplified):
<template>
<input
v-bind="$attrs"
:value="value"
#blur="handleBlur"
>
</template>
<script>
export default {
inheritAttrs: false,
props: {
value: {
type: String,
default: ''
}
},
mounted () {
console.log(this.value) // displays nothing, whereas it should display "John Doe"
},
methods: {
handleBlur (e) {
this.$emit('input', e.target.value)
}
}
}
</script>

value prop is used with the emitted event input to do the v-model job, so you should give your prop another name like defaultValue to avid this conflict:
<custom-input
v-model="userEmail"
defaultValue="John Doe"
/>
and
<template>
<input
v-bind="$attrs"
:value="value"
#blur="emitValue($event.target.vaklue)"
>
</template>
<script>
export default {
inheritAttrs: false,
props: {
value: {
type: String,
default: ''
},
defaultvalue: {
type: String,
default: ''
},
},
mounted () {
this.emitValue(this.defaultValue)
},
methods: {
emitValue(val) {
this.$emit('input', val)
}
}
}
</script>

Related

Vue v-model binding form Checkbox to Boolean variable is passing an Event object instead of true/false

I´m using a Vue base component which wraps a simple check box. In my template Im doing a 2-way bind using a v-model to a Boolean variable in my data. Nothing too fancy but there´s a problem with my implementation where instead of the target variable receiving a true/false value when the control state is turned on/off (check/unchecked), it receives an event object. I don't know what I'm doing wrong, any ideas?
I see this exception in the console when I click the control:
[Vue warn]: Invalid prop: type check failed for prop "value". Expected
Boolean, got Event
found in
<BaseSwitch> at src/components/BaseSwitch.vue
<Card> at src/components/Card.vue
<SlideYUpTransition>
<Modal> at src/components/Modal.vue
<RFQSales> at src/views/rfq/RFQSales.vue
<FadeTransition>
<App> at src/App.vue
<Root>
base-switch component:
<template>
<label class="custom-toggle">
<input type="checkbox"
v-model="model"
v-bind="$attrs"
v-on="$listeners">
<span class="custom-toggle-slider rounded-circle"></span>
</label>
</template>
<script>
export default {
name: "base-switch",
inheritAttrs: false,
props: {
value: {
type: Boolean,
default: false,
description: "Switch value"
}
},
computed: {
model: {
get() {
return this.value;
},
set(value) {
this.$emit("base-switch", value);
}
}
}
};
</script>
<style>
</style>
Template:
<base-switch class="pull-right" v-model="modals.modalNewRFQ.data.borrowButton"
You haven't stated the version of Vue you use. Is it Vue2 or Vue3?
Your tag is also not fully copy/pasted. I have changed it to
<base-switch class="pull-right" v-model="borrowButton" #base-switch="process($event)"></base-switch>
Now your code works perfectly with Vue3.
Here is the link to the working playground:
https://stackblitz.com/edit/vue-f3nqz1?file=src/App.vue
For Vue3 you must define the emitted events, to make it work well.
emits: ['base-switch'],
Also, pay attention to passing value:
this.$emit("base-switch", value);
and
#base-switch="process($event)">
If your function gets an Even Object, then you can try JSON.stringify() it to check the properties. Possibly, it is related to your $listeners solution. I couldn't guess it to reproduce the problem.
Here is the code:
App.vue
<template>
<div id="app">
<base-switch class="pull-right" v-model="borrowButton" #base-switch="process($event)"></base-switch> <br />
Value: {{value}}
</div>
</template>
<script>
import BaseSwitch from './components/BaseSwitch.vue'
export default {
name: 'App',
components: {
BaseSwitch
},
data() {
return {
borrowButton: false,
value: null
}
},
methods: {
process(value) {
this.value = value;
}
}
}
</script>
BaseSwitch.vue
<template>
<label class="custom-toggle">
<input type="checkbox"
v-model="model"
v-bind="$attrs"
>
<span class="custom-toggle-slider rounded-circle"></span>
</label>
</template>
<script>
export default {
name: "base-switch",
inheritAttrs: false,
emits: ['base-switch'],
props: {
value: {
type: Boolean,
default: false,
description: "Switch value"
}
},
computed: {
model: {
get() {
return this.value;
},
set(value) {
this.$emit("base-switch", value);
}
}
}
};
</script>
<style>
</style>

Vue 2: Value prop does not update input value

So I have encountered a weird issue with my code, that I hope to get some help with.
I have a custom "Input" component where I have a normal HTML input with some styling. I have then called this component with a value and a function to call upon changes. No v-model is used as I have to do some validation on the field. However, it doesn't work. I can see that the value variable in the "Input" component changes correctly, but it does not impact the HTML input element at all, if you enter multiple values into the input field. How can this be?
InputComponent
<template>
<label class="block text-sm flex justify-end lg:justify-start w-28 h-10">
<span class="text-gray-800">{{ label }}</span>
<input
class="block text-black placeholder:text-black placeholder:opacity-40 w-14 lg:w-full rounded-lg text-center"
:placeholder="placeholder"
:type="type"
:value="value"
#input="handleInput($event.target.value)"
/>
</label>
</template>
<script>
export default {
props: {
label: String,
placeholder: String,
type: String,
value: String,
size: String,
},
methods: {
handleInput(value) {
this.$emit('input', value);
}
},
}
</script>
Page component calling Input
<template>
<Input
type="number"
placeholder="0"
size="sm"
:value="test"
#input="changePlannedVacationDay($event)"
/>
</template>
<script>
export default {
data() {
return {
test: ""
};
},
methods: {
changePlannedVacationDay(value) {
let localValue = value;
const maxValue = 5;
if (parseInt(localValue) < 0) {
localValue = "0";
} else if (parseInt(localValue) > maxValue) {
localValue = maxValue.toString();
}
this.test = localValue;
}
},
</script>
You should use a computed property with getter and setter:
<template>
<input v-model="localModel">
</template>
<script>
export default
{
name: 'CustomInputComponent',
props:
{
value:
{
type: String,
default: null,
},
},
computed:
{
localModel:
{
get()
{
return this.value;
},
set(val)
{
this.$emit('input', val);
}
},
},
}
</script>
In the parent component you should use a watcher to detect value changes and act upon them.

Update child component value on axios response using v-model

Vue 3
I am trying to update the value of the data variable from the Axios response. If I print the value in the parent component it's getting printed and updates on the response but the variable's value is not updating in the child component.
What I am able to figure out is my child component is not receiving the updated values. But I don't know why is this happening.
input-field is a global component.
Vue 3
Parent Component
<template>
<input-field title="First Name" :validation="true" v-model="firstName.value" :validationMessage="firstName.validationMessage"></input-field>
</template>
<script>
export default {
data() {
return {
id: 0,
firstName: {
value: '',
validationMessage: '',
},
}
},
created() {
this.id = this.$route.params.id;
this.$http.get('/users/' + this.id).then(response => {
this.firstName.value = response.data.data.firstName;
}).catch(error => {
console.log(error);
});
},
}
</script>
Child Component
<template>
<div class="form-group">
<label :for="identifier">{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input :id="identifier" :type="type" class="form-control" :class="validationMessageClass" :placeholder="title" v-model="inputValue">
<div class="invalid-feedback" v-if="validationMessage">{{ validationMessage }}</div>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
},
validation: {
type: Boolean,
required: false,
default: false,
},
type: {
type: String,
required: false,
default: 'text',
},
validationMessage: {
type: String,
required: false,
default: '',
},
modelValue: {
required: false,
default: '',
}
},
emits: [
'update:modelValue'
],
data() {
return {
inputValue: this.modelValue,
}
},
computed: {
identifier() {
return this.title.toLowerCase().replace(/ /g, '-').replace(/[^\w-]+/g, '');
},
validationMessageClass() {
if (this.validationMessage) {
return 'is-invalid';
}
return false;
}
},
watch: {
inputValue() {
this.$emit('update:modelValue', this.inputValue);
},
},
}
</script>
The reason your child will never receive an update from your parent is because even if you change the firstName.value your child-component will not re-mount and realize that change.
It's bound to a property that it internally creates (inputValue) and keeps watching that and not the modelValue that's been passed from the parent.
Here's an example using your code and it does exactly what it's supposed to and how you would expect it to work.
It receives a value once (firstName.value), creates another property (inputValue) and emits that value when there's a change.
No matter how many times the parent changes the firstName.value property, the child doesn't care, it's not the property that the input v-model of the child looks at.
You can do this instead
<template>
<div class="form-group">
<label :for="identifier"
>{{ title }}
<span class="text-danger" v-if="validation">*</span>
</label>
<input
:id="identifier"
:type="type"
class="form-control"
:class="validationMessageClass"
:placeholder="title"
v-model="localValue" // here we bind localValue as v-model to the input
/>
<div class="invalid-feedback" v-if="validationMessage">
{{ validationMessage }}
</div>
</div>
</template>
<script>
export default {
... // your code
computed: {
localValue: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
};
</script>
We remove the watchers and instead utilize a computed property which will return the modelValue in it's getter (so whenever the parent passes a new value we actually use that and not the localValue) and a setter that emits the update event to the parent.
Here's another codesandbox example illustrating the above solution.

How to pass dynamic props with Vue

I need to pass props using Vue, I thought of JSON with object that includes name and value. I need to pass data to a different component but it changes as in each event the names and values change.
So for example I might have name: 'a' value: 'b', name: 'f' value: 'k' and in anorher event name: 'c' value: 'd'
my code that works but it work because i return hard coded data
data() {
return {
params: {
name:'bill',
value:'jones'
},
in child
#Component({
props:
{
urls: {
type: Object,
default: () => { return {name:'', value: ''} }
},
}
function with object params that i need to get the data from
getParams(url) {
paramsData[key] = value;
//console.log(key,value);
}
return params;
console.log(params)
You can use computed property names
emitEvent(name, value) {
let objectToEmit = {
[name]: value,
};
this.$emit("event-name", objectToEmit);
}
Now name and value will be set according to whatever you pass in emitEvent function.
You can read more about computed property names on below link
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer
You can pretty much pass anything through and you can do it in many ways. Here are 4 examples:
Note: for all options we are assuming you have a parent component that is using the following syntax
<example-component :name="somename" :value="somevalue"></example-component>
Option1: props as a list of array of strings. The values can be anything in JS i.e. numbers, strings, objects, functions
<template>
<div>
<p v-text="example"></p>
</div>
</template>
<script>
export default {
name: "ExampleComponent",
prop: ['name','value']
}
</script>
Option 2: most common approach. Every prop to be a specific type of value. In these cases, you can list props as an object, where the properties’ names and values contain the prop names and types, respectively
<template>
<div>
<p v-text="example"></p>
</div>
</template>
<script>
export default {
name: "ExampleComponent",
props: {
name: {
type: String,
required: false,
default: 'something'
},
value: {
type: Number,
required: true
}
},
}
</script>
Option 3: you can validate or in this case pass in an Object and defaults returned from a factory function so you will always have some value. You can even return validation validator: function (value) {...}
<template>
<div>
<!-- some value from EventServiceClass-->
<p v-text="example.name"></p>
</div>
</template>
<script>
import EventServiceClass from "./EventServiceClass";
export default {
name: "ExampleComponent",
props: {
example: {
type: Object,
default: function () {
return {name:'a', value: 'b'}
}
},
},
}
</script>
Option 4: a little more advanced but in this example we are bind get and set to input properties on a form, which we would use to create factory form components
<template>
<div>
<input
id="name"
type="text"
v-model="name"
class="form--input light"/>
</div>
</template>
<script>
export default {
name: "ExampleComponent",
props: {
name: {
type: String,
default: ""
}
},
computed: {
name: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value);
}
}
}
}
</script>

Two way data binding with :model.sync when prop has get() and set()

I have a computed property that I use as v-model on an input. I've written it this way to get reactivity -- this calls my setText Vuex action which I then can get with my getter text. It looks like this:
text: {
get() {
return this.text;
},
set(value) {
this.setText(value);
},
},
and I use it in my input like this:
<input class="input" type="text" v-model="text" />
This works well. Now, I've put the input in question into a separate component which I use. This means I have to pass the text v-model as props, which I do with :model.sync, like so:
<myInput :model.sync="text"/>
and in the myInput component I use the props like so:
<input class="input" id="search-order" type="text" :value="model" #input="$emit('update:model', $event)">
But this doesn't seem to work at all, whenever I type into the input, the input says: [object InputEvent] and if I try to see and the value of model it's {isTrusted: true}. I'm assuming it's because of the getters and setters I have on my computed property. How do I pass these down to the child component?
Instead of using the .sync modifier you can support the v-model directive in your custom component. v-model is syntax sugar for a value prop and an input event.
To support v-model just make sure your custom component has a value prop and emits an input event with the new value: this.$emit('input', event.target.value).
Here is an example of a <BaseInput> component I use, it's written in TypeScript:
<template>
<input
:type="type"
:value="value"
class="input"
v-on="listeners"
>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
name: 'BaseInput',
props: {
type: {
type: String,
default: 'text',
},
value: {
type: [String, Number],
default: '',
},
lazy: {
type: Boolean,
default: false,
},
number: {
type: Boolean,
default: false,
},
trim: {
type: Boolean,
default: false,
},
},
computed: {
modelEvent(): string {
return this.lazy ? 'change' : 'input'
},
parseModel(): (value: string) => string | number {
return (value: string) => {
if (this.type === 'number' || this.number) {
const res = Number.parseFloat(value)
// If the value cannot be parsed with parseFloat(),
// then the original value is returned.
return Number.isNaN(res) ? value : res
} else if (this.trim) {
return value.trim()
}
return value
}
},
listeners(): Record<string, Function | Function[]> {
return {
...this.$listeners,
[this.modelEvent]: (event: HTMLElementEvent<HTMLInputElement>) =>
this.$emit(this.modelEvent, this.parseModel(event.target.value)),
}
},
})
</script>
You can use it like so:
<BaseInput v-model="text" />