can not initialize data from prop - vue.js

here is my child component:
export default {
name: "SnackBar",
props: ["show", 'msg', 'progress'],
data: () => ({
show2: this.show,
}),
}
and its template:
<template>
<div class="text-center">
<v-snackbar
v-model="this.show2"
:multi-line="true"
timeout="-1"
>
{{ msg }}
<template v-slot:action="{ attrs }">
<v-progress-circular
v-if="progress"
style="float: right"
indeterminate
color="red"/>
<v-btn
color="red"
text
v-bind="attrs"
#click="show2 = false">
Close
</v-btn>
</template>
</v-snackbar>
</div>
</template>
and I use it in the parent :
<SnackBar :show="snackbar.show" :msg="snackbar.msg" :progress="snackbar.progress"/>
the problem is that the show2 data is not defined:, here is the console log:
Property or method "show2" 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.
from official docs:
The prop is used to pass in an initial value; the child component
wants to use it as a local data property afterwards. In this case,
it’s best to define a local data property that uses the prop as its
initial value:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
I am doing exactly the same thing, what is the problem?

Using the arrow function for the data property loses the vue component context, try to use a simple function :
export default {
name: "SnackBar",
props: ["show", 'msg', 'progress'],
data(){
return {show2: this.show,}
},
}
or try out this :
export default {
name: "SnackBar",
props: ["show", 'msg', 'progress'],
data: (vm) => ({
show2: vm.show,
}),
}

Related

Parent variable not updated when updating trough child component

I am trying to create a few custom form fields for my page and i learned that i cannot use props to do so so i am trying to find a way to update my parent component variable when i use my child component. Whe i check the parent variable it is always empty.
Here is my component:
<template>
<input
v-model="value"
:placeholder="placeHolder"
class="form-field"
>
</template>
<script>
export default {
props: ['placeHolder'],
data() {
return {
value: ''
}
},
methods: {
updateValue(){
this.$emit("update-text", this.value);
}
},
watch: {
value: function(){
this.updateValue
}
}
}
</script>
And this is how i use the component:
<TextField placeholder="Nome" :update-text="name = value"/>
what exactly am i doing wrong?
I am using vue.js with nuxt.js
I think a simpler approach in this case might be emitting an input event from your custom text field and binding the component to the variable using v-model.
TextField.vue
<template>
<input
#input="$emit('input', $event.target.value)"
:placeholder="placeHolder"
class="form-field"
>
</template>
<script>
export default {
props: ['placeHolder']
}
</script>
Usage
<template>
<TextField placeholder="Nome" v-model="name"/>
</template>
<script>
export default {
data: () => ({
name: '',
}),
}
</script>
Read more about using v-model on custom components here.

Vue mutating a property that is used as "v-model"

Many issue like this but I cannot find my way. I simplified the code to explain my problem...
Just wanted a reusable feedback message to show with the result of the rest api using vuetity andsnackbar widget.
In the parent component:
<Feedback :active="hasFeedback" :msg="feedbackMsg" />
The Feedback component:
<template>
<v-snackbar v-model="active" >
{{ msg }}
<v-icon #click="active = false">mdi-close-thick</v-icon>
</v-snackbar>
</template>
<script>
export default {
components: {},
props: ["active", "msg"]
};
</script>
I tried to add computed property, methods, getter, setter but always got an error.
Just copy your active prop to some prop in a data section of a component and use and change that prop in the data section.
<template>
<v-snackbar v-model="isActive" >
{{ msg }}
<v-icon #click="isActive = false">mdi-close-thick</v-icon>
</v-snackbar>
</template>
<script>
export default {
components: {},
props: {
active: {
type: Boolean,
default: false
}, {
msg: {
type: String
}
},
data () {
return {
isActive: this.active
}
}
};
</script>

Vue - v-model inside a component within a component

I'm trying to separate my project now into components to make the code readable when adjusting into a responsive app. The problem is passing the v-model from base-select -> child -> parent. How do I store the data selected to the Parent.vue items: ''? Here is my code below.
Parent.vue
<template>
<child></child>
</template>
<script>
import Child from './components/Child'
export default {
components: {
Child,
},
data: ()=> ({
item: ''
})
}
</script>
Child.vue
<template>
// Random HTML
// Random HTML 2
<base-select
:items="select"
>
</template>
<script>
import BaseSelect from '#/components/BaseSelect'
export default {
components: {
BaseSelect,
},
data: ()=> ({
select: ['Select 1', 'Select 2']
})
}
</script>
BaseSelect.vue
<template>
<v-select
v-bind="$attrs"
v-on="$listeners"
class="body-2"
solo
dense
clearable
/>
</template>
To implement v-model you need to add a value property to each child component. Each component will also need to emit an input event so that the parent component can pick up the change (read more here). Note that if you are passing data down through too many components, you should probably look at using Vuex however in this case it would probably still be fine.
Your components would have to look something like this to pass v-model all the way to the base component:
Parent.vue
<template>
<!-- Pass the data item below -->
<child v-model="item"></child>
</template>
<script>
import Child from './components/Child'
export default {
components: {
Child,
},
data: ()=> ({
item: ''
})
}
</script>
Child.vue
<template>
// Random HTML
// Random HTML 2
<base-select
:items="select"
value="value"
#input="e => $emit('input', e)"
>
</template>
<script>
import BaseSelect from '#/components/BaseSelect'
export default {
components: {
BaseSelect,
},
// We add the value prop below to work with v-model
props: {
value: String
},
data: ()=> ({
select: ['Select 1', 'Select 2']
}),
}
</script>
BaseSelect.vue
<template>
<v-select
v-bind="$attrs"
v-on="$listeners"
value="value"
#input="e => $emit('input', e)"
class="body-2"
solo
dense
clearable
/>
</template>
<script>
export default {
props: {
value: String
}
}
</script>
You can find a similar working example that I did here.
You need to use $emit (documentation) to passing data back to parent components. Or you can start using Vuex (state manager for Vue.js).
You also can check the live demo here.

Extending Vue.js SFC components

I haven't found a good resource on extending Vue.js components. In every project I've worked on, regardless of the UI component library that's used, there are application Base components which extend the UI library components to enforce company/application defaults and standards.
I'm trying to extend Vue-Multiselect: https://vue-multiselect.js.org/ which has about 30 props and 12 slots. The component I'm extending doesn't matter -- I only mention it because ideally I don't want to have to repeat 30 props and 12 slots in my implementation.
I simply want to make two changes to the behavior of the component:
Make disabled prop a bit smarter
The Vue-Multiselect component has a standard disabled prop which works as expected:
<Multiselect :disabled="isDisabled" ...>
In our application, we have global state in Vuex which determines if the application is read-only. What I want to avoid is requiring developers to pass this state to every form field:
<Multiselect :disabled="readOnly || isDisabled" ...>
<OtherComponent :disabled="readOnly || someOtherCondition" ...>
...
So the user of my base component should only need to be concerned about their local UI state which affect the disabled status:
<BaseCombo :disabled="!emailValid" ...>
This would handle the 90% case of form fields that are locked down when the application is read-only and I can use an additional prop for cases where we want to ignore the global read-only status.
<BaseCombo :disabled="!emailValid" :ignoreReadOnly="true" ...>
Provide defaults
Secondly, I simply want to override some of the default prop values. This post addresses the question of supplying defaults:
https://stackoverflow.com/a/52592047/695318
And this works perfectly until I tried to modify the behavior of the disabled prop I mentioned previously.
My attempt to solve this was to either wrap or extend the component. I'd really want to avoid redeclaring all of the props if possible.
<template>
<Multiselect
:disabled="myCustomDisabled"
:value="value"
#input="$emit('input', $event)"
:options="options"
:label="label"
:track-by="trackBy"
:placeholder="placeholder"
... repeat for all 30 options
<script>
import Multiselect from 'vue-multiselect'
export default {
name: "BaseCombo",
extends: Multiselect, // extend or simply wrap?
computed: {
myCustomDisabled() {
this.props.disabled || ... use disabled from Vuex state
}
},
props: {
disabled: Boolean,
placeholder: {
type: String,
default: 'My Default Value',
},
... repeat for all props
The problem I ran into is I don't know how to handle the slots. The user of this BaseCombo should still be able to use all 12 slots in the VueMultiselect component.
Is there a better solution for extending components?
You can use this.$props to access props defined in the props attribute. Similarly you can access attributes (things you haven't defined as props) with this.$attrs. Finally you can bind props with v-bind="someVariable".
If you combine this you can do something like this:
<!-- App.vue -->
<template>
<component-a msg="Hello world" :fancy="{ test: 1 }" />
</template>
<!-- ComponentA.vue -->
<template>
<component-b v-bind="$attrs" />
</template>
<script>
export default {
name: 'componentA'
}
</script>
<!-- ComponentB.vue -->
<template>
<div>
{{ msg }}
{{ fancy }}
</div>
</template>
<script>
export default {
props: {
msg: String,
fancy: Object
},
mounted () {
console.log(this.$props);
}
}
</script>
In this example, component B would be the component you try to extend.
Here's a complete example based on Sumurai8's answer and motia's comments.
<template>
<Multiselect v-bind="childProps" v-on="$listeners">
<slot v-for="(_, name) in $slots" :name="name" :slot="name" />
<template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData">
<slot :name="name" v-bind="slotData" />
</template>
</Multiselect>
</template>
<script>
import Multiselect from 'vue-multiselect'
export default {
name: "BaseCombo",
props: {
placeholder: {
type: String,
default: 'This is my default',
},
disabled: {
type: Boolean,
default: false,
},
},
components: {
Multiselect,
},
computed: {
childProps() {
return { ...this.$props, ...this.$attrs, disabled: this.isDisabled };
},
appReadOnly() {
return this.$store.state.appReadOnly;
},
isDisabled() {
return this.disabled || this.appReadOnly;
}
},
}
</script>

VueJS change v-model variable from child

I'm trying to change the v-model of a component by the parent component most I'm not getting.
In the parent component I have a showProgress variable, I want it when I change it to true the child v-model <progress-modal> switch to true.
ProgressModal.vue
<template>
<v-dialog v-model="show" persistent max-width="400">
<v-card :dark="($theme === 'dark')">
<v-card-title class="headline" v-text="title"></v-card-title>
<v-divider></v-divider>
<div class="text-xs-center mt-2">
<v-progress-circular indeterminate :size="100" :color="$color"></v-progress-circular>
<v-card-text v-text="text"></v-card-text>
</div>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'progress-modal',
props: ['title', 'text'],
data: () => ({
show: true
}),
methods: {
}
}
</script>
I already tried to use
<progress-modal v-model="showProgress">
Instead of v-model in v-dialog but it does not work :(
Pass value prop as value to v-dialog component, and re-emit input from v-dialog component:
//CustomDialog.vue
<v-dialog :value="value" #input="$emit('input', $event)">
</v-dialog>
...
props:['value']
and add v-model to your parent (custom dialog)
//Parent.vue
<custom-dialog v-model="showProgress">
Example
To enable usage of v-model by the parent, you have to define a value prop in the child and use it.
<template>
<v-dialog v-model="value" persistent max-width="400">
...
</template>
<script>
export default {
name: 'progress-modal',
props: ['title', 'text', 'value'], // added 'value'
data: () => ({
...
</script>
This way, when you use:
<progress-modal v-model="showProgress">
...the value inside progress-modal will have the value of parent's showProgress.
Keeping it named show
To use other internal name instead of value you can declare the model option in the component.
<template>
<v-dialog v-model="show" persistent max-width="400">
...
</template>
<script>
export default {
name: 'progress-modal',
props: ['title', 'text', 'show'], // added 'show'
model: { // added model option
prop: 'show' //
}, //
data: () => ({
}), // in this case, remove show from data
...
</script>