Properly alert prop value in parent component? - vuejs2

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.

Related

Vue why props is undefined on component

I try to transfer data to my component using props
If I type the name in the parent, I see that the button is working properly, ie the problem is with the component.
Where am I wrong?
Thanks in advance
Parent:
HTML
<div><button #click="changeName" :name="name">Change my name</button></div>
Script:
import UserDetail from "./UserDetail.vue";
export default {
components: {
UserDetail,
UserEdit,
},
data() {
return {
name: "Eden",
};
},
methods: {
changeName() {
this.name = "EdenM";
},
},
};
Component
<template>
<div>
<p>{{ name }}</p>
</div>
</template>
<script>
export default {
props: ["name"],
};
</script>
Ooooooh!! I add the prop an wrong place!
Here is my ans:
<user-detail :name="name"></user-detail>

how to create a dynamic vue component that has a computed template containing another component with Object properties without passing it as string

I have a component like this:
Relation.vue
<template>
<div :is="dynamicRelation"></div>
</template>
<script>
import Entry from '#/components/Entry';
import weirdService from '#/services/weird.service';
export default {
name: 'Relation',
data() {
return {
entry1: { type: 'entity', value: 'foo', entity: {id: 4}},
entry2: { type: 'entity', value: 'bar', entity: {id: 5}},
innerText: '#1 wut #2',
}
},
computed: {
dynamicRelation() {
return {
template: `<div>${this.innerText
.replace('#1', weirdService.entryToHtml(this.entry1))
.replace('#2', weirdService.entryToHtml(this.entry2))}</div>`,
name: 'DynamicRelation',
components: { Entry }
};
}
}
}
</script>
wierd.service.js
export default {
entryToHtml(entry) {
[some logic]
return `<entry entry='${JSON.stringify(entry)}'></entry>`;
// unfortunately I cannot return JSX here: <entry entry={entry}></entry>;
// I get 'TypeError: h is not a function'
// unless there is a way to convert JSX to a pure html string on the fly
}
}
Entry.vue
<template>
<div>{{objEntry.name}}</div>
</template>
<script>
export default {
name: 'Entry',
props: {
entry: String // I need this to be Object
},
computed: {
objEntry() {
return JSON.parse(this.entry);
}
}
}
</script>
The innerText property decides how the components will be rendered and it can be changing all the time by having its # slots in any position.
In this example the result is:
<div>
<div>foo</div>
wut
<div>bar</div>
</div>
This works since Entry component has as a property entry that is of type String but I have to JSON.stringify() the entry object in weirdService side and then in Entry component I have to JSON.parse() the string to get the real object back.
How can I make the above work so that I pass an object directly to a dynamic template so I avoid serialization and deserialization all the time.
btw for this to work runtimeCompiler needs to be enabled in vue.config.js:
module.exports = {
runtimeCompiler: true
}
I know I can use JSX to return components with objects in them but this is allowed only in render() function it seems and not custom ones like mine.
Thanks!!
I was able to do what I wanted by using JSON.stringify still but pass the entry as object :entry
wierd.service.js
export default {
entryToHtml(entry) {
return `<entry :entry='${JSON.stringify(entry)}'></entry>`;
}
}
Entry.vue
<template>
<div>{{entry.name}}</div>
</template>
<script>
export default {
name: 'Entry',
props: {
entry: Object
}
}
</script>

Vue: Using input value in function

I am using Single File Components and I have a modal component that has an
input box but I can't get the value of the input in a function below using the v-modal name. It keeps coming back as 'name is not defined'. Am I using the v-model attribute incorrectly?
<template>
<input v-model="name" class="name"></input>
</template>
<script>
export default {
methods: {
applyName() {
let nameData = {{name}}
}
}
}
</script>
You're right, you're using the v-model property incorrectly.
First off you need to define a piece of state in your component, using data:
export default {
data: () => ({
name: '',
}),
methods: {
log() {
console.log(this.name);
}
}
}
You can then bind this piece of data in your component using v-model="name", just like you did. However, if you want to access this piece of state in your method, you should be using this.name in your applyName() method.
Your {{name}} syntax is used to get access to the data in your template, like so:
<template>
<span>
My name is: {{name}}!
</span>
</template>
You have to use this pointer to access the model:
<template>
<input v-model="inputName" class="name"></input>
</template>
<script>
export default {
data() {
return {
inputName: '',
}
},
methods: {
applyName() {
// Notice the use of this pointer
let nameData = { name: this.inputName };
}
}
}
</script>
Look at the doc https://v2.vuejs.org/v2/guide/forms.html#v-model-with-Components
In the template, you are referring by name to data, computed or methods. In this case, it refers to data. When the input changes the name then the data is updated.
It is possible to use in a function referring to this.
<template>
<input v-model="name" class="name"></input>
</template>
<script>
export default {
data() {
return { name: '' }
},
methods: {
applyName() {
let nameData = this.name
}
}
}
</script>

VueJS Passing computed data as a prop returns undefined

I've tried and tried, but i can't figure it out the problem. From what I could read elsewhere, the variable passed to the child component gets sent as undefined before the data is available in the parent.
Please see here for reference:
the code in codesandbox
<template>
<div id="app">
<child :parentData="data.message"/>
</div>
</template>
<script>
import Child from "./components/Child";
export default {
name: "App",
components: {
Child
},
computed: {
quote() { return 'Better late than never' }
},
data() {
return {
data: { message: this.quote } ,
thisWorks: { message: "You can see this message if you replace what is passed to the child" }
};
}
};
</script>
Then in the child:
<template>
<div>
<h1>I am the Child Component</h1>
<h2> {{ parentData }}</h2>
</div>
</template>
<script>
export default {
name: "Child",
props: {
parentData: { type: String, default: "I don't have parent data" },
},
};
</script>
The answer is, you cannot access the value of this.quote because at the moment the data objectis creating, the computed object actually does not exist.
This is an alternative, we will use the created() lifecycle hook to update the value of data object:
created(){
this.data = {
message: this.quote
}
},
You don't need to change any things, just adding those line of codes is enough.
I've already tested those codes in your CodeSandbox project and it works like a charm.
Hopefully it helps!

Using v-model with a prop on VUE.JS

I'm trying to use a data coming from a prop with v-model, the following code works, but with a warning.
<template>
<div>
<b-form-input v-model="value" #change="postPost()"></b-form-input>
</div>
</template>
<script>
import axios from 'axios';
export default {
props: {
value: String
},
methods: {
postPost() {
axios.put('/trajectory/inclination', {
body: this.value
})
.then(response => {
})
.catch(e => {
this.errors.push(e)
})
}
}
}
</script>
The warning says:
"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: "value"
So I changed and now I'm using a data as the warning says.
<template>
<div>
<b-form-input v-model="_value" #change="postPost()"></b-form-input>
</div>
</template>
<script>
import axios from 'axios';
export default {
props: {
value: String
},
data() {
return {
_value: this.value
}
},
methods: {
postPost() {
axios.put('/trajectory/inclination', {
body: this._value
})
.then(response => {
})
.catch(e => {
this.errors.push(e)
})
}
}
}
So now the code it's not working and the warning says:
"Property or method "_value" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option"
Any idea how to fix the first code to suppress the warning? (or some idea on how to fix the second code?)
Obs.: b-form-input it's not my componente, this is the Textual Input from Boostrap-Vue (Doc for b-form-input)
Answer is from https://github.com/vuejs/vue/issues/7434
Props are read-only, but you are trying to change its value with v-model. In this case, if you change the input value, the prop is not modified and the value is restored on the next update.
Use a data property or a computed setter instead:
computed: {
propModel: {
get () { return this.prop },
set (value) { this.$emit('update:prop', value) },
},
},
https://v2.vuejs.org/v2/guide/computed.html#Computed-Setter
Bert addresses your direct issue, but I think you should also know that your approach is a bit off. Since ultimately you are sending the new value to postPost, you don't really need to modify your local copy. Use the event object that is sent to the change handler to get the current value from the input.
Instead of v-model, just use :value, and don't include the invocation parentheses when specifying the change handler.
<template>
<div>
<b-form-input :value="value" #change="postPost"></b-form-input>
</div>
</template>
<script>
import axios from 'axios';
export default {
props: {
value: String
},
methods: {
postPost(event) {
axios.put('/trajectory/inclination', {
body: event.target.value
})
.then(response => {
})
.catch(e => {
this.errors.push(e)
})
}
}
}
</script>
_ prefixed properties are reserved for Vue's internal properties.
Properties that start with _ or $ will not be proxied on the Vue
instance because they may conflict with Vue’s internal properties and
API methods.
Try changing _value to something that doesn't start with an underscore.
One general workaround is to introduce a data-variable and watch the props to update-variable. This is quite subtle and so easy to get wrong so here's an example with a Vuetify modal using v-model (the same technique, in theory, should work with <input> and others):
<template>
<v-dialog v-model="isVisible" max-width="500px" persistent>
</v-dialog>
</template>
<script>
export default {
name: 'Blablabla',
props: {
visible: { type: Boolean, required: true }
},
data() {
isVisible: false
},
watch: {
// `visible(value) => this.isVisible = value` could work too
visible() {
this.isVisible = this.$props.visible
}
}
}
</script>
The official Vue docs shows how to use v-model on a custom component: https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
TL;DR:
You simply need to have a specifically named value prop, and emit an input event which the v-model when you instantiate the component maps for you.
More info on how this works on the link above.
<template>
<input
type="text"
:value="value"
#input="$emit('input', $event.target.value)"
/>
</template>
<script>
export default {
name: "Input",
props: {
value: String,
},
};
</script>
<Input v-model="searchText"></Input>
Point your input v-model directive to a data property named value_ (or any other name not starting with prefixes _ or $ which are reserved by Vue). Set the data property's default value to null. Then, add a method getValue() which will set property value_ based on your value prop's value. Finally, call getValue() in Vue's created() lifecycle hook. Like so:
<template>
<div>
<b-form-input v-model="value_" #change="postPost()">
</b-form-input>
</div>
</template>
<script>
import axios from 'axios';
export default {
data: () => ({
value_: null
}),
props: {
value: String
},
methods: {
postPost() {
axios.put('/trajectory/inclination', {
body: this.value_
})
.then(response => {
})
.catch(e => {
this.errors.push(e)
})
},
getValue() {
this.value_ = this.value;
}
},
created() {
this.getValue()
}
}
</script>
You can use a data like below.
<template>
<input type="text" v-bind:value="value" v-on:input="dValue= $event.target.value" />
</template>
<script>
export default {
props: ["value"],
data: function () {
return {
dValue: this.value,
};
},
methods: {
alertValue() {
alert("Current Value" + this.dValue);
},
},
};
</script>