I'm trying, with no luck to use some data that get passed into a vue component. I have no problem receiving it, but i don't get how i can manipulate it the way i want.
In the component i want to se its "title" variable to the value passed, if any. But if no value is passed i want to set it to a default value, which in this case is "lorem". I cant use the props default value attribute for a reason that does not matter here.
How would i do this?
Code:
<Meta title="test" />
Component:
<template>
<div></div>
</template>
<script>
export default {
name: "Meta",
data() {
return {
title: ""
};
},
computed: {
thetitle() {
return this.title ? "" : "lorem ipsum";
}
},
mounted() {
document
.querySelector('meta[name="description"]')
.setAttribute("content", this.title);
}
};
title should be defined inside the props option since it's received from the parent component :
export default {
name: "Meta",
props:['title'];
computed: {
thetitle() {
return this.title ? "" : "lorem ipsum";
}
},
mounted() {
document
.querySelector('meta[name="description"]')
.setAttribute("content", this.title);
}
};
let's take some scenario to understand this.
export default {
name: "Meta",
props:{
title: {
type: [String,null,undefined],
defualt: 'lorem'
}
};
computed: {
modifiedTitle() {
return this.title || "lorem ipsum";
}
},
mounted() {
document
.querySelector('meta[name="description"]')
.setAttribute("content", this.modifiedTitle);
}
};
1. test="randon truethy string"
<Meta title="test" />
here we are passing a string in title. so the default function of title props will not call. and in modified title props this.title get truthy value .so return value of modifiedTitle is 'randon truthy string'
2.test ="" or null
<Meta title="test" />
here we are passing a string as an empty string or null. even in this case also default function will not call. and modifiedTitle props this.title is falsy value so these will return "lorem ipsum".
3.pass test = undefined
<Meta title="test" />
here we are passing undefined. so in this case default function of props is called as a set default value to the title which is "lorem". in modifiedTitle props this.title has truthy value (which is set by default function -> 'lorem'). so modified computedProps return 'lorem'
4. Don't pass the test as props
<Meta />
in this, we are not passing props. so it will call the default function and further value set according to point 3. it also returns 'lorem' in modifiedTitle props.
Suggestion : Do not use built-in or reserved HTML elements as component id: meta
Observation : this.title ? "" : "lorem ipsum" - This condition is not correct. It will return empty if title passed. Hence, You can use simple if statement :
if (!this.title) {
return 'lorem ipsum'
}
Live Demo :
Vue.component('metaapp', {
computed: {
theTitle: function() {
if (!this.title) {
return 'lorem ipsum'
}
return this.title;
}
},
props: ['title'],
template: '<h3>{{ theTitle }}</h3>'
});
var app = new Vue({
el: '#app',
data: {
msg: ''
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<MetaApp :title="msg"></MetaApp>
</div>
Related
I need to modify and update value in mongoDB.
I use a form to do it.
How to pre-fill an Input type text with value ?
You can assign your value from mongoDB to the v-model you are using for input in mounted hook:
<script>
export default {
data() {
return {
message: ''
}
},
methods: {
async getDataFromMongoDB(){
// your existing code here...
return 'Value from DB'
}
},
async mounted() {
this.message = await this.getDataFromMongoDB()
},
}
</script>
<template>
<input v-model="message" />
</template>
Can someone tell me why I get undefined when trying to pass a prop down to child component?
Error: Cannot read properties of undefined (reading 'title')
Sandbox reproduction: https://codesandbox.io/s/little-silence-3lgd9?file=/components/PostEditor.vue
Parent component:
<template>
<div>
<PostEditor v-bind="post" />
</div>
</template>
<script>
export default {
name: "PostsEdit",
data() {
return {
post: {},
};
},
async created() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts/1"
);
this.post = await response.json();
},
};
</script>
Child component:
<template>
<section>
{{ title }}
</section>
</template>
<script>
export default {
post: {
type: Object,
default: () => {},
required: false,
},
data() {
return {
title: this.post.title,
};
},
methods: {
save() {
this.$emit("save", {
...this.formValues,
});
},
},
};
</script>
v-bind="post"
The child prop is named "post", but the parent is trying to bind several properties with v-bind="post".
The binding name should match the target prop in the child:
<!-- <PostEditor v-bind="post" /> --> ❌ binds subproperties of post
<PostEditor v-bind:post="post" /> ✅ binds post
<PostEditor :post="post" /> ✅ binds post (shorthand)
post default prop value
The child's post prop has a default option of () => {}, but that arrow function returns nothing (undefined), as the curly brackets start a block scope. This is effectively the same as not having the default option.
You likely meant for default to return an empty object, which requires wrapping the curly braces in parentheses:
// default: () => {} // ❌ returns undefined
default: () => ({}) // ✅ returns {}
data() not reactive
Even with the prop default fixed above, the formValues.title property in data() is initialized to this.post.title, which would be undefined because post is initially an empty object. The post value passed in from the parent is updated asynchronously, so the value is still the initial value from the parent (also an empty object) in data().
Note that data() is not reactive, so it's only called once at initialization. Changes to this.post will not automatically update the data property.
Solution: Render child after post populated
One solution is to defer rendering the child component until the post prop is populated in the parent so that the post prop would have the expected values for the child's data() initialization.
In the parent, initialize post to null, and use v-if="post" to conditionally render the child:
<template>
<div> 👇
<PostEditor :post="post" v-if="post" />
</div>
</template>
<script>
export default {
data() {
return { 👇
post: null,
}
},
}
</script>
demo
In the child component, you need to define the props section first and then access it through computed
export default {
props: { // change added
post: {
type: Object,
default: () => {},
required: false,
}
}, // change added
computed: {
title() {
return this.post ? this.post.title : '',
}
},
I am trying to create a dynamic 'quick input' form with Vue.
A simple text input that has a dynamic data key so that I can change what I'm submitting to axios. I couldn't figure out how to get a dynamic key name coming from a prop eg
data() {
return {
DYNAMIC-NAME-FROM-PROP: value
}
}
So I've got a values: {} bit of data that gets filled by the props instead.
The code below achieves everything EXCEPT pre-rendering the existing value.
See the comments next to v-model in the tempate:
<template>
<div class="relative">
<input
type="text"
v-model="values[fieldName]" // This is not rendering on load
// v-model="values[this.$props.field]" -> 'this' is null error #input
#keydown.enter.prevent="submit"
/>
</div>
</template>
<script>
export default {
props: ["userId", "field", "preFill"],
data() {
return {
values: {},
fieldName: this.$props.field,
};
},
mounted() {
this.values[this.$props.field] = this.$props.preFill;
},
methods: {
submit() {
axios.post(`/admin/${this.userId}/update`, this.values).then(response => {
// success
});
}
}
};
</script>
Am I going about this completely wrong?
Or am I 'nearly there' and just need to fix the v-model render issue?
To set a dynamic key name on an object in javascript, it turns out you can use square brackets, as in:
{
[keyName] : value
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names
So my code is fixed by simply passing the prop as the key in the axios call:
submit() {
axios
.post(`/admin/${this.userId}/update`, {
[this.$props.field]: this.value
})
.then(response => {
// we out here
});
}
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>
When I try to update my custom text-area component's model data this.message='<span id="foo">bar</span> the text and html does not display in the htmltextarea tag like it should, but I can see the update applied in the Vue dev tool's console. I've also tried switching to an object instead of a string and using Vue.set, but this does not work either.
Any suggestions on how to fix this?
The goal with the htmlTextArea component is to get the users text from the htmlTextArea tag (this works), manipulate this text and bind it back to the textarea, but with HTML in it.
Custom text-area component:
<template>
<div contenteditable="true" #input="updateHTML" class="textareaRoot"></div>
</template>
<script>
export default {
// Custom textarea
name: 'htmlTextArea',
props:['value'],
mounted: function () {
this.$el.innerHTML = this.value;
},
methods: {
updateHTML: function(e) {
this.$emit('input', e.target.innerHTML);
}
}
}
</script>
Other component:
<template>
...
<htmlTextArea id="textarea" v-model="message"></htmlTextArea>
...
</template>
<script>
data: {
return {
message: 'something'//this works
}
}
...
methods: {
changeText() {
this.message='<span id="foo">bar</span>'//this does not
}
},
components: {
htmlTextArea
}
</script>
You need to set the value explicitly after the value props change. you can watch for value change.
<template>
<div contenteditable="true" #input="updateHTML" class="textareaRoot"></div>
</template>
<script>
export default {
// Custom textarea
name: "htmlTextArea",
props: ["value"],
mounted: function() {
this.$el.innerHTML = this.value;
},
watch: {
value(v) {
this.$el.innerHTML = v;
}
},
methods: {
updateHTML: function(e) {
this.$emit("input", e.target.innerHTML);
}
}
};
</script>
Change the data property into a function, as you have it defined it is not reactive.
data () {
return {
message: 'something'//this works
}
}
Now when you update the message property in your method, the component will update accordingly.
Reactivity in depth