VueJS pass default prop without reference to child component - javascript

I've stumbled upon this situation where I want to pass a prop to a child component that will be the default value of the component, but it will only be showed when the initial value is empty.
Parent Component:
<multi-line-input v-model="data.something" placeholder="Enter Something" :default="data.something"/>
Child Component
props: {
value: {
type: String,
default: ''
},
default: {
type: String,
default: ''
},
},
methods: {
emitBlur (e) {
if (!this.value && this.default) {
this.value = this.default
}
this.$emit('blur')
},
emitInput () {
this.$emit('input', this.$el.value)
}
}
So what I am trying to achieve basically, is when the component loads will get the value from v-model it will also receive a default value that shouldn't change, and only used as a value when the actual value is empty on blur
The default will have the initial value of data.something and it should not change!
I tried to get rid of the reference using JSON.parse(JSON.stringify(this.value)) but it doesn't seem to work either!

So if I understand your question correctly, you want this behavior: upon the blur event on your <multi-line-input> component, if the value of the input is empty, then set the value to a default value which is specified by the parent (through a prop).
First of all, it is an error to do this.value = ... in your component. You must not modify props, props pass data from parent to child only, the data passed through props is not yours to modify directly from within the component.
Try something like this:
Vue.component('multi-line-input', {
template: '<input #blur="onBlur" #input="onInput" :value="value">',
props: {
value: {
type: String,
default: '',
},
default: {
type: String,
default: '',
},
},
methods: {
onBlur() {
if (!this.value && this.default) {
this.$emit('input', this.default);
}
},
onInput(e) {
this.$emit('input', e.target.value);
},
},
});
new Vue({
el: '#app',
data: {
user: null,
initialUser: null,
},
created() {
// Pretend that I'm pulling this data from some API
this.user = {
name: 'Fred',
email: 'fred#email.com',
address: '123 Fake St',
};
// Make a copy of the data for the purpose of assigning the
// default prop of each input
this.initialUser = _.cloneDeep(this.user);
},
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="app">
<template v-if="user">
<multi-line-input v-model="user.name" :default="initialUser.name"></multi-line-input>
<multi-line-input v-model="user.email" :default="initialUser.email"></multi-line-input>
<multi-line-input v-model="user.address" :default="initialUser.address"></multi-line-input>
</template>
</div>
Or, if you want the default value to be determined by the component instead of the parent (through a prop), you can do something like this instead:
Vue.component('multi-line-input', {
template: '<input #blur="onBlur" #input="onInput" :value="value">',
props: {
value: {
type: String,
default: '',
},
},
created() {
this.def = this.value;
},
methods: {
onBlur() {
if (!this.value && this.def) {
this.$emit('input', this.def);
}
},
onInput(e) {
this.$emit('input', e.target.value);
},
},
});
new Vue({
el: '#app',
data: {
user: null,
},
created() {
// Pretend that I'm pulling this data from some API
this.user = {
name: 'Fred',
email: 'fred#email.com',
address: '123 Fake St',
};
},
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="app">
<template v-if="user">
<multi-line-input v-model="user.name"></multi-line-input>
<multi-line-input v-model="user.email"></multi-line-input>
<multi-line-input v-model="user.address"></multi-line-input>
</template>
</div>
However I do not recommend the second approach because the child component instance will only every have one default value for its entire lifetime. Vue reuses component instances whenever possible, so it wouldn't work if Vue were to bind it to a different parent component (how/when would it update its own default state?).

Related

VueJs 3 - Custom Input Component

I'm trying to build a custom HTML <input> component for VueJS3. I've been following this tutorial:
https://dev.to/viniciuskneves/vue-custom-input-bk8
So far I managed to get the CustomInput.vue component to work and emit the modified value back to the parent App.Vue.
<template>
<label>
{{ label }}
<input type="text" :name="name" :value="value" #input="onInput" #change="onChange" />
</label>
</template>
<script>
export default {
name: 'CustomInput',
props: {
label: {
type: String,
required: true,
},
value: {
type: String,
required: true,
},
},
computed: {
name() {
return this.label.toLowerCase();
},
},
methods: {
onInput(event) {
this.$emit('input', event.target.value);
},
onChange(event) {
this.$emit('change', event.target.value);
},
},
}
</script>
What I don't understand is - how will the emitted events be detected by the parent App.vue component? I can't see it happens, and I can't find it in the tutorial.
My App.Vue looks like this:
<template>
<custom-input :label="'Name'" :value="name"></custom-input>
<div>{{ name }}</div>
</template>
<script>
import customInput from "./components/CustomInput.vue";
export default {
components: { customInput },
name: "App",
data: function () {
return {
name: "",
};
},
mounted() {
this.name = "Thomas";
},
};
</script>
Thanks in advance for any help :-)
This tutorial is for Vue 2 - for Vue 3 there is another tutorial (https://www.webmound.com/use-v-model-custom-components-vue-3/)
Emitting input event works in Vue 2 only - for Vue 3 you will have to emit update:modelValue and also use modelValue as a prop instead of just value.
You can do it right in your template.
<custom-input :label="'Name'" :value="name" #change='name=$event' #input='name=$event'></custom-input>
You can also use a method or computed with setter as well.

Make a reactive component with vuejs

I need a Vue component to show some HTML content in v-data-table from Vuetify. I have seen this post Vue 2 contentEditable with v-model, and I created a similar code shown below.
My problem is the component is not reactive. When I click the "Test button", no content is updated in HtmlTextArea.
<template>
<div>
<v-btn #click="doTest()">Test Button</v-btn>
<HtmlTextArea
v-model="content"
style="max-height:50px;overflow-y: scroll;"
></HtmlTextArea>
</div>
<template>
export default {
name: "ModelosAtestados",
components: { HtmlTextArea },
data: () => ({
content: "",
}),
methods: {
doTest() {
this.content = "kjsadlkjkasfdkjdsjkl";
},
},
};
//component
<template>
<div ref="editable" contenteditable="false" v-on="listeners"></div>
</template>
<script>
export default {
name: "HtmlTextArea",
props: {
value: {
type: String,
default: "",
},
},
computed: {
listeners() {
return { ...this.$listeners, input: this.onInput };
},
},
mounted() {
this.$refs.editable.innerHTML = this.value;
},
methods: {
onInput(e) {
this.$emit("input", e.target.innerHTML);
},
},
};
</script>
This occurs because HtmlTextArea sets the div contents to its value prop only in the mounted lifecycle hook, which is not reactive.
The fix is to setup a watcher on value, so that the div contents are updated to match whenever a change occurs:
// HtmlTextArea.vue
export default {
watch: {
value: {
handler(value) {
this.$refs.editable.innerHTML = value;
}
}
}
}
demo
In the #click event binder, you have to pass a function. You passed the result of an executed function.
To make it work: #click="doTest" or #click="() => doTest()".
How to debug such problems:
Display the value you want to update on your template to check if its updated: {{content}}
Use the vue devtool extension to check the current state of your components

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
}
}

Reactive properties error in vue-cli

hello,
I have a few problem with vue-cli.
I try to display (in the main component) the text which are enter in the input (in the child component). it's work (so strange) but there are an error message :
vue.esm.js?efeb:578 [Vue warn]: Property or method "test" 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. See:
https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-
Properties.
found in
---> <Signup> at src/components/auth/Signup.vue
<App> at src/App.vue
<Root>
I search on internet and there a lot of example to resolve this error but not with the architecture vue-cli. I don't understand that...
Step by step :
I write a component :
<template>
<div class="container">
<p>{{ data.test }}</p>
<form #submit.prevent="signup">
<v-customInput v-model="test" #onChangeValue="onChange"></v-customInput>
</form>
</div>
</template>
<script>
import CustomInput from '../shared/CustomInput'
export default {
name: 'HelloWorld',
components: {
'v-customInput': CustomInput,
},
data() {
return {
data: {
test: '',
first_name: '',
last_name: '',
email: '',
password: '',
vat: 0,
creation_company_date: new Date(),
phone: '',
},
error: false,
};
},
methods: {
onChange(variable) {
const data = this.data;
for (let value in data) {
if (value === 'test') {
data[value] = variable;
}
}
}
},
};
</script>
and a child Components :
<template>
<div class="customInput">
<input v-model="value" type="text">
<label>First Name</label>
<script>
export default {
name: 'CustomInput',
data() {
return {
value: '',
};
},
watch: {
value: function(val, oldVal) {
this.$emit('onChangeValue', this.value);
}
},
};
</script>
In vue-2.x, if you bind a property using v-model and that property doesn't exist (test in this case), then you will get the error.
Try this: added test property.
<template>
<div class="container">
<p>{{ data.test }}</p>
<form #submit.prevent="signup">
<v-customInput v-model="test" #onChangeValue="onChange"></v-customInput>
</form>
</div>
</template>
<script>
import CustomInput from '../shared/CustomInput'
export default {
name: 'HelloWorld',
components: {
'v-customInput': CustomInput,
},
data() {
return {
test: '', // <===== initialize test with a default value
data: {
test: '',
first_name: '',
last_name: '',
email: '',
password: '',
vat: 0,
creation_company_date: new Date(),
phone: '',
},
error: false,
};
},
methods: {
onChange(variable) {
const data = this.data;
for (let value in data) {
if (value === 'test') {
data[value] = variable;
}
}
}
},
};
</script>

Updating VueJS component data attributes when prop updates

I'm building a VueJS component which needs to update the data attributes when a prop is updated however, it's not working as I am expecting.
Basically, the flow is that someone searches for a contact via an autocomplete component I have, and if there's a match an event is emitted to the parent component.
That contact will belong to an organisation and I pass the data down to the organisation component which updates the data attributes. However it's not updating them.
The prop being passed to the organisation component is updated (via the event) but the data attibute values is not showing this change.
This is an illustration of my component's structure...
Here is my code...
Parent component
<template>
<div>
<blink-contact
:contact="contact"
v-on:contactSelected="setContact">
</blink-contact>
<blink-organisation
:organisation="organisation"
v-on:organisationSelected="setOrganisation">
</blink-organisation>
</div>
</template>
<script>
import BlinkContact from './BlinkContact.vue'
import BlinkOrganisation from './BlinkOrganisation.vue'
export default {
components: {BlinkContact, BlinkOrganisation},
props: [
'contact_id', 'contact_name', 'contact_tel', 'contact_email',
'organisation_id', 'organisation_name'
],
data () {
return {
contact: {
id: this.contact_id,
name: this.contact_name,
tel: this.contact_tel,
email: this.contact_email
},
organisation: {
id: this.organisation_id,
name: this.organisation_name
}
}
},
methods: {
setContact (contact) {
this.contact = contact
this.setOrganisation(contact.organisation)
},
setOrganisation (organisation) {
this.organisation = organisation
}
}
}
</script>
Child component (blink-organisation)
<template>
<blink-org-search
field-name="organisation_id"
:values="values"
endpoint="/api/v1/blink/organisations"
:format="format"
:query="getQuery"
v-on:itemSelected="setItem">
</blink-org-search>
</template>
<script>
export default {
props: ['organisation'],
data() {
return {
values: {
id: this.organisation.id,
search: this.organisation.name
},
format: function (items) {
for (let item of items.results) {
item.display = item.name
item.resultsDisplay = item.name
}
return items.results
}
}
},
methods: {
setItem (item) {
this.$emit('organisationSelected', item)
}
}
}
</script>
How can I update the child component's data properties when the prop changes?
Thanks!
Use a watch.
watch: {
organisation(newValue){
this.values.id = newValue.id
this.values.search = newValue.name
}
}
In this case, however, it looks like you could just use a computed instead of a data property because all you are doing is passing values along to your search component.
computed:{
values(){
return {
id: this.organisation.id
search: this.organisation.name
}
}
}

Categories

Resources