Check if data/prop are passed or not? - vue.js

I got a component that i pass in some data to. I want to check if "title" is passed or not, and conditionally set its value. As it works now, the value i pass just overrides. How would i do that?
<Meta title="test" />
Component
<template>
<vue-headful
:title="this.title"
/>
</template>
<script>
import vueHeadful from "vue-headful";
export default {
name: "Meta",
components: { vueHeadful },
data() {
return {
title: "default val"
};
}
};

In your Meta component you are not defining the prop, but a variable in data. You should delete the title from data and add a prop:
props:
{title: {
type: String,
default: 'default val'
}},
See documentation on props
You also do not need this. before title in your template.
<vue-headful :title="title" /> will be enough.

Related

2 Way Databind components within Components

I am struggling to reuse my components.
I want to pass the data passed to my component as a prop to another component.
If I do that vue complains about a mutation of the prop.
Example:
I have contacts that I want to show on multiple location of my app.
For that I created a contact component to reuse it:
<template>
<div>
<input :value="contact.firstName" #input="$emit('update:contact', {...contact, firstName: $event.target.value})">
<Mother v-model:mother="contact.mother"/>
</div>
</template>
<script>
import Mother from '#/components/Mother'
export default {
name: 'Contact',
components: {
Mother
},
props: {
contact: Object,
},
emit: ['update:contact'],
methods: {
}
}
</script>
Every contact has a mother, mother are shown in other places not only in the contact component.
That is why I created a mother component, that is used by the contact.
<template>
<div>
<input :value="mother.lastName" #input="$emit('update:mother', {...mother, lastName: $event.target.value})">
</div>
</template>
<script>
export default {
name: 'Mother',
props: {
mother: Object,
},
emit: ['update:mother'],
methods: {
}
}
</script>
Now I want to be able to mutate the contact an the mother as well, and I want to be able to use two contact components on the same site.
If I use it the way explained I get this error:
ERROR Failed to compile with 1 error 09:17:25
error in ./src/components/Contact.vue
Module Error (from ./node_modules/eslint-loader/index.js):
/tmp/vue-example/src/components/Contact.vue
4:27 error Unexpected mutation of "contact" prop vue/no-mutating-props
✖ 1 problem (1 error, 0 warnings)
I have an example project showing my problem:
https://gitlab.com/FirstWithThisName/vue-example.git
Thanks for your help
First I need to assume a few points.
You wanted to use v-model.
You wanted the component to be chained.
Working Example here on Vue SFC Playground.
*Note that the import path is different on the example site.
App.vue
<template>
<Contact v-model="contact" />
{{ contact }}
</template>
... remaining code omitted
Contact.vue
<template>
<div>
<input v-model="localValue"/>
<Mother v-model="childValue" />
</div>
</template>
<script>
import Mother from "./Mother.vue"
export default {
name: "Contact",
components: {
Mother
},
props: {
modelValue: Object,
},
mounted(){
this.childValue = this.modelValue.mother
},
data: () => ({
localValue: "",
childValue: null
}),
watch:{
updatedData(){
this.$emit('update:modelValue', this.updatedData)
}
},
computed: {
updatedData() {
return { firstName: this.localValue, mother: this.childValue };
},
},
};
</script>
Mother.vue
<template>
<div>
<input
v-model="localValue"
#input="$emit('update:modelValue', updatedData)"
/>
</div>
</template>
<script>
export default {
name: "Mother",
props: {
modelValue: Object,
},
data: () => ({
localValue: "",
}),
computed: {
updatedData() {
return { ...this.modelValue, lastName: this.localValue };
},
},
};
</script>
As you might know, props cannot be mutated, so you will need to "make a copy" of the value on each component to process locally.
If Mother component are never going to be used separately, v-model can be split into v-on and v-bind instead.
Lastly, as for recommendation, chaining like this can become very messy if the data starts to grow or the depth level increases. You could just make another Wrapper component that contains Contact and Mother component that scales horizontally instead.
Depends on how complex your application will get.
One option is two-way data-binding as explained here:
https://v3.vuejs.org/guide/component-basics.html#using-v-model-on-components
So you basically emit the changes to the parent.
For more complex applications I wouldn't pass data that are used in multiple components as props, but use a store. Either a simple reactive object; with provide/inject or use something like Vuex.

Vue 3 two way binding with select box

I'm trying to create a two way binding between my parent (create user form) and a child component (reusable selectbox).
The parent component
<template>
<Selectbox :selectedOption="selectedRole" :options="roles" />
<span>SelectedRole: {{ selectedRole }}</span>
</template>
<script>
import Selectbox from '#/components/formElements/Selectbox.vue';
export default {
components: {
Selectbox,
},
async created() {
await this.$store.dispatch('roles/fetchRoles');
this.selectedRole = this.roles[0].value;
},
data() {
return {
selectedRole: null,
};
},
computed: {
roles() {
return this.$store.getters['roles/roles'].map((role) => ({
value: role.id.toString(),
label: role.name,
}));
},
},
};
</script>
I'm passing down the roles as options and the selectedRole variable as selectedOption.
The child component
<template>
<select :value="selectedOption" #input="(event) => $emit('update:selectedOption', event.target.value)">
<option v-for="option in options" :value="option.value" :key="option.value">{{ option.label }}</option>
</select>
</template>
<script>
export default {
props: {
options: {
type: Array,
required: true,
},
selectedOption: {
type: String,
required: false,
},
},
};
</script>
The selectedOption is assigned to the value together. When another value is selected I want to update the passed down value in the parent component. Therefore I'm using an $emit function but that's not working right now.
I also tried to use v-model to combine the value and change attributes but without success.
<select v-model="selectedOption">
What's the correct way?
Code: Codesandbox
I guess this is the handling you want to achieve: https://codesandbox.io/s/practical-orla-i8n3t?file=/src/components/Selectbox.vue
If you use v-model on a sub-component, you have to handle it properly in the sub-component.
<custom-select v-model="value" />
<!-- IS THE SAME AS -->
<custom-select
:modelValue="value"
#update:modelValue="value = $event"
/>
So if you use v-model, a property with the name modelValue gets passed down to the sub-component. If the modelValue changes (which means another option in the select list gets selected) you have to emit a change event, indicating that the modelValue got changed: $emit('update:modelValue'). v-model automatically updates it's value if this event occurs.
Source: https://learnvue.co/2021/01/everything-you-need-to-know-about-vue-v-model/

How to bind a local component's data object to an external component

how do you use a local component's data attriutes to bind an external component's v-model
for example i have this component
<publish-blog>
<VueTrix v-model="form.editorContent">
</publish-blog>
so the form.editorContent there refers to the publish-blog component's form.editorContent inside data, how do I do that ?
You can pass a prop to the publish-blog component.
This would be what ever page or component you are using the publish blog on, though to be honest I'm not sure why you would not just put the VueTrix component inside of the publish-blog component.
This would be on what ever page/component you are wanting it on.
<template>
<PublishBlog :trix="trix">
<VueTrix v-model="trix" />
</PublishBlog>
</template>
<script>
import PublishBlog from './PublishBlog.vue';
export default {
components: {
PublishBlog,
},
data() {
return {
trix: '',
};
},
};
</script>
and inside of the publish blog component make the form.editorContent the prop passed or a default value.
But without a global store/state you are stuck with props.
UPDATE: Showing what a publish blog component might look like
PublishBlog.vue
<template>
<section>
what ever goes here.
<slot />
</section>
</template>
<script>
export default {
name: 'PublishBlog',
props: {
trix: {
type: String,
default: '',
},
},
data() {
return {
form: {
editorContent: this.trix
},
};
},
};
</script>

Properly alert prop value in parent component?

I am new to Vue and have been very confused on how to approach my design. I want my component FileCreator to take optionally take the prop fileId. If it's not given a new resource will be created in the backend and the fileId will be given back. So FileCreator acts as both an editor for a new file and a creator for a new file.
App.vue
<template>
<div id="app">
<FileCreator/>
</div>
</template>
<script>
import FileCreator from './components/FileCreator.vue'
export default {
name: 'app',
components: {
FileCreator
}
}
</script>
FileCreator.vue
<template>
<div>
<FileUploader :uploadUrl="uploadUrl"/>
</div>
</template>
<script>
import FileUploader from './FileUploader.vue'
export default {
name: 'FileCreator',
components: {
FileUploader
},
props: {
fileId: Number,
},
data() {
return {
uploadUrl: null
}
},
created(){
if (!this.fileId) {
this.fileId = 5 // GETTING WARNING HERE
}
this.uploadUrl = 'http://localhost:8080/files/' + this.fileId
}
}
</script>
FileUploader.vue
<template>
<div>
<p>URL: {{ uploadUrl }}</p>
</div>
</template>
<script>
export default {
name: 'FileUploader',
props: {
uploadUrl: {type: String, required: true}
},
mounted(){
alert('Upload URL: ' + this.uploadUrl)
}
}
</script>
All this works fine but I get the warning below
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:
"fileId"
What is the proper way to do this? I guess in my situation I want the prop to be given at initialization but later be changed if needed.
OK, so short answer is that the easiest is to have the prop and data name different and pass the prop to the data like below.
export default {
name: 'FileCreator',
components: {
FileUploader
},
props: {
fileId: Number,
},
data() {
return {
fileId_: this.fileId, // HERE WE COPY prop -> data
uploadUrl: null,
}
},
created(){
if (!this.fileId_){
this.fileId_ = 45
}
this.uploadUrl = 'http://localhost:8080/files/' + this.fileId_
}
}
Unfortunately we can't use underscore as prefix for a variable name so we use it as suffix.

Use default value if prop argument is null

I have this simplified avatar component:
<template>
<img :src="src">
</template>
<script>
export default {
name: 'Avatar',
props: {
src: {
type: String,
default: '/static/avatar-default.png'
}
}
}
</script>
Let's say I fetch some user data from my API and it contains no avatar URL. In such case I want this component to use the default value but it only seems to work when passing undefined to it, but undefined is not valid in JSON so I cannot return that from the API response.
Is there a way to realize what I want by passing in null or is there a better way to handle this?
I would make a computed property based on the src prop value that will return a default value if the src is null:
<template>
<img :src="source">
</template>
<script>
export default {
name: 'Avatar',
props: {
src: { type: String }
},
computed: {
source() {
return this.src || '/static/avatar-default.png';
}
}
}
</script>
You could also explicitly pass undefined as prop if src is null and you'd rather want the component to handle default values.
<template>
<img :src="src || undefined">
</template>
This should do the trick:
<template>
<img :src="src || '/static/avatar-default.png'">
</template>
And personally, I would keep the default value for the prop in addition to coercing null values to the default value.
As far as I know, you cannot achieve what you want through the prop definition.