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.
Related
I'm trying to understand why changing a prop, used to provide an initial value, doesn't cause the data variable to change.
In the below example, changing the initialName value passed in the parent component, also causes the initialName to change in the child component. However, name keeps the value it was originally initialized as. I believed, seemingly incorrectly, that changing a prop would re-render the component.
ChildComponent.vue
<template>
<div>
{{initialName}}
{{name}}
</div>
</template>
<script>
export default {
props: {
initialName: {
type: String,
default: '',
}
},
data() {
return {
name: this.initialName,
};
},
</script>
ParentComponent.vue
<template>
<ChildComponent :initialName="AnExampleName"/>
</template>
<script>
import ChildComponent from ChildComponent.vue
export default {
components: {
ChildComponent
}
</script>
I've been able to work around this by watching the prop and updating name, however this doesn't feel like the best approach.
How come changing the prop doesn't change the data? Is there a better way to pass an initial value to a child component?
data is meant to be static, so if you set it once, it will not be reactive afterwards.
It will change if you mutate it directly of course, like this this.name = 'updated value hi hi'.
But it will not mutate if you update another prop/state elsewhere (like initialName in your example).
A simple approach for this would be to use a computed like this
<script>
export default {
props: {
initialName: {
type: String,
default: "",
},
},
computed: {
name() {
return this.initialName
}
},
};
</script>
This example proves that the usage of data is NOT updating the name value (initialName is totally reactive) if you do use it like the OP did. The usage of computed solves this issue tho.
In the tutorial of vue.js, we have this code
<script>
export default {
data() {
return {
text: ''
}
},
methods: {
onInput(e) {
this.text = e.target.value
}
}
}
</script>
<template>
<input :value="text" #input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</template>
And I don't understand why when I delete the bind on value, the two way binding is still working ?
In the tuto, it says that using the v-on & v-bind allow to do two way binding
Am I missing something ?
The Vue example is sort of a bad use case, a little simple for what it's trying to convey:
v-on is for assigning event listeners, so v-on:click="doSomething(value)"
v-bind is binding the actual value of vue data/state. So example:
<button v-on:click="setUserDetails(value)" v-bind:value="user.id">Click</button>
Imagine this component:
<template>
<input :value="value"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
And now a simple usage of it:
<template>
<MyComp v-model="passwd" type="password" minlength="3" #focus="onFocus"/>
</template>
<script>
export default {
name: 'MyOtherComp',
data(){
return {
passwd: ''
}
},
methods:{
onFocus(){}
}
}
</script>
As you can see, value, type, and minlength properties and focus event are bidden to MyComp.
Now question: How can I handle extra props in MyComp? they are not defined in MyComp props. Vue gathers them in a special variable called $attrs, which is a normal JS object. Vue also gathers all events into $listeners variable.
Now inside MyComp these special variables are:
$atrrs:{
type: 'password',
minlength: '3'
}
$listerners:{
focus: /* function onFocus from parent */
}
To redirect these values:
<template>
<input :value="value" v-bind="$attrs" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
As you can see, we use v-bind to bind extra props, and we use v-on to bind (redirect) events. The result is:
<input :value="value" :type="$attrs.type" :minlength="$attrs.minlength" #focus="$listeners.focus"/>
Of course you can use these directions to bind you objects too:
<template>
<input :value="value" v-bind="$attrs" v-bind="accumulated" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
},
data(){
return {
accumulated:{
maxlenght: (+this.$attrs.minlength || 2) + 30, // It's just for a practice to use extra props inside JS code :-)
rows: 5,
}
}
}
}
</script>
Keep in mind that duplicate props will replace and the last one wins.
I have a component like this:
<script>
export default {
name: "Thing",
props: {
min: Number,
value: Number
} ...
-
<template>
...
<input type="number" v-bind:min=min/10 step="0.1" v-model = value aria-hidden="true" />
...
</template>
I generally use millimeters, while I want to display things in centimeter. So for example this component will be initialized with value = 50 (50mm), but I want the input to display 5 (5cm).
How can I change "value" in the component to be value/10 upon initialization?
Also I need to pass some tests , so something like "Just do value/10 in the parent before initializing the component" wont work Im afraid.
You can add a computed to the component as follows:
computed: {
minimum() {
return this.min / 10;
}
}
And use it as follows:
<h1>{{ minimum }}</h1>
This way, you pass the prop like this:
:min="min"
Found the answer myself:
<script>
export default {
name: "Thing",
props: {
min: Number,
value: Number
},
created: function() {
this.value = this.value/10;
},...
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.
<template>
<div
v-editable="blok"
class="util__flex">
<component
v-for="blok in blok.columns"
:key="blok._uid"
:blok="blok"
:is="blok.component"/>
</div>
</template>
<script>
export default {
props: ['blok']
}
</script>
Im doing tutorial at Storyblok, and I do get such an error.
https://www.storyblok.com/tp/nuxt-js-multilanguage-website-tutorial#creating-the-homepage-components
Props should at least define their types vue/require-prop-types
You have probably enabled ESlint on project initialization (see create-nuxt-app options), that activated this mandatory rule.
So you have to declare a following type:
String
Number
Boolean
Array
Object
Date
Function
Symbol
See Vue.js doc:
https://v2.vuejs.org/v2/guide/components-props.html#Prop-Types
https://v2.vuejs.org/v2/guide/components-props.html#Type-Checks
For your case:
<script>
export default {
props: {
blok: Object
}
}
</script>
For current nuxt version(v2.8.1), we should set props as follows:
<script>
export default {
props: {
blok: {
type: Object,
default: null
}
}
}
</script>