I want to post json data from Vue to php, but I'm struggling to find a way to pass input value data from Vue component to root element.
When I call method submitProduct, alert message simply gives me 'undefined'.
I had to strip my original code because of that stupid post balance policy.
What's wrong here?
var productForm = Vue.createApp ({
methods:{
submitProduct: function(){
alert(JSON.stringify(productForm.product))
this.postData(productForm.product)
},
postData: function(p){
fetch('mysql_postdata.php', {
method: 'POST',
mode: "same-origin",
credentials: "same-origin",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({p:p})
//body: 'd='+d
})
}
}
})
productForm.component('custom-form', {
props: ["value"],
template: `
<label>SKU<input type="text" :value=this.product.sku></label>
<label>Name<input type="text" :value=this.product.name></label>
<label>Price<input type="text" :value=this.product.price></label>
` ,
data: function() {
return {
product: {
sku: 0,
name: 'asdf',
price: 0
},
options: ['Size', 'Weight', 'Dimensions'],
selected: 'Size'
}
}
})
productForm.component('status-bar', {
props: ['info'],
template: '<p>{{ selected }}</p>'
})
const vm = productForm.mount('#product_form')
The product state belongs to the custom-form component so the root app cannot access the state.
If you trying to create a form and get the input from the form, you need to do 1 of this:
Lift the state to the root and pass down the custom-form and bind an event to listen to the state change docs here. (only do this if the custom-form component is not deep down in the component tree)
Using the state management store like Vuex to share the state within the app (in case the child component is deep down in the tree you have to pass the state down so many levels, using store management will solve that). docs here. If your app is really small consider the provide/inject API.
Another choice is using the provide/inject API (similar to the context provider in react).
First of all after 3.5 days of struggling to try to understand Vue I came with tested successfull result. I want to thank you and anybody else, who helped me to understand basics principles in Vue! :)
Please see link below...
https://jsfiddle.net/e2mnh4xa/
P.S. And yes! You are right about rearranging 'custom-form' tag. :)
html code:
<div id="product_form" v-cloak>
<custom-form>
</custom-form>
</div>
js code:
var productForm = Vue.createApp ({})
productForm.component('custom-form', {
props: {
modelValue: {
type: String,
default: ''
},
},
components: ['status-bar'],
template: `
<button v-on:click="submitProduct">Save</button>
<label>SKU<input type="text" v-model="this.product.sku"></label>
<label>Name<input type="text" v-model="this.product.name"></label>
<label>Price<input type="text" v-model="this.product.price"></label>
` ,
data: function() {
return {
product: {
sku: 0,
name: 'asdf',
price: 0,
},
options: ['Size', 'Weight', 'Dimensions'],
selected: 'Size',
outputData: ''
}
},
computed:{
model:{
get(){ return this.modelValue },
set(v){ this.$emit('update:modelValue',v)}
}
},
methods:{
submitProduct: function(){
alert (this.showData());
return this.postData(this.product)
},
showData: function(){
console.log(this.product.sku)
return JSON.stringify(this.product)
}
}
})
const vm = productForm.mount('#product_form')
Related
I still have trouble why certain ways of changing data work, while others do not. I tried this example:
watch: {
'$store.state.linedata': function() {this.redraw()}
},
methods: {
redraw() {
this.chartOptions.series[0].data = this.$store.state.linedata
}
},
data() {
return {
chartOptions: {
chart: {
type: this.type
},
series: [{
data: this.$store.state.linedata,
name: "Test Series",
color: this.color
}]
}
}
}
This setup works, whenever I change the linedata in my store, the component gets updated. However, for me it would be more intuitive to update the data like this, without referencing this.chartOptions.series[0].data:
redraw() {
this.$store.state.linedata = [1,2,3,4,5,6]
}
This will update the state correctly, but however not cause to update the component with the new data. Why does the second way not work and is my first way the correct way to do it? I feel like I am missunderstanding some core concept here.What would a best practice look like?
Thank you!
From the Vuex docs you can read:
The only way to actually change state in a Vuex store is by committing a mutation
It means that you should not try to do this.$store.state.linedata = [1,2,3,4,5,6]. It may throw an error depending on your Vuex settings in the console by the way. Instead, create a mutation like so:
mutations: {
updateLineDate(state, lineDate) {
state.lineData = lineData;
}
}
And then call:
this.$store.commit("updateLineDate", [1, 2, 3, 4, 5, 6]);
To automatically update your chart's data, I would suggest creating a computed property in your Vue component. To make it reactive to changes, map your attribute using mapState:
import { mapState } from "vuex";
// ...
computed: {
...mapState("lineData"),
chartData() {
return {
chart: {
type: this.type
},
series: [{
data: this.lineData,
name: "Test Series",
color: this.color
}]
}
}
}
Then remember to provide chartData to your chart component instead of chartOptions in my example.
Here's a part of my Vue template:
<ul>
<li v-for="friend in user.friends">
<span v-if="checkIfNew(friend.id)"> ... </span>
</li>
</ul>
Basically, friends is an array of objects, and I want to display the span element, if we have new messages from any of them. That's what checkIfNew() does. It checks whether the friend's id is in the unreadIds array (it contains ids of friends, who sent a message)
This array is being updated in a different method, but, here's the problem: v-if doesn't react to the changes.
Here's a part of the script section:
data(){
return {
unreadIds: []
}
},
methods:{
checkIfNew(id){
if(id in this.unreadIds) return true
else return false
}
},
computed:{
user(){
return this.$store.getters.user;
}
}
Does anyone have any ideas what am I doing wrong?
id in this.unreadIds doesn't do what you think it does. See the docs for the in operator. It will return true if the object has the value as a property. So if this.unreadIds had 3 items and you had an id of 1, then the in operator will return true because 1 is a property of the array (this.unreadIds[1] exists).
Instead, you should use includes.
Try rewriting your method like this:
checkIfNew(id) {
return this.unreadIds.includes(id);
}
Here's a working version of the component that updates the list without the Vuex store code:
<ul>
<li v-for="friend in user.friends" :key="friend.ids">
<span v-if="checkIfNew(friend.id)">{{ friend.name }}</span>
</li>
</ul>
export default {
data() {
return {
unreadIds: [5],
user: {
friends: [
{ id: 1, name: 'joe' },
{ id: 5, name: 'nancy' },
{ id: 9, name: 'sue' },
],
},
};
},
created() {
setTimeout(() => this.unreadIds.push(9), 2000);
},
methods: {
checkIfNew(id) {
return this.unreadIds.includes(id);
},
},
};
Just to prove here that David was correct all along, I put this code in a runable snippet, and cannot find any fault...
Vue.config.productionTip = false
new Vue({
el: '#app',
data() {
return {
unreadIds: [],
};
},
created() {
setTimeout(() => this.unreadIds.push(9), 2000);
},
methods: {
checkIfNew(id) {
// if(id in this.unreadIds) return true
// else return false
return this.unreadIds.includes(id);
},
},
computed: {
user(){
return {
friends: [
{ id: 1, name: 'joe' },
{ id: 5, name: 'nancy' },
{ id: 9, name: 'sue' }
]
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<div id="app">
<ul>
<li v-for="friend in user.friends" >
<span v-if="checkIfNew(friend.id)">{{ friend.name }}</span>
</li>
</ul>
</div>
The sample above is a bit closer to the original question
user is a computed not data
unreadIds is initially empty
Upvote from me!
You want to leverage Vue's reactivity system as the previous answers do not.. they will eventually open you up to inexplicable problems that aren't easily debuggable.
Rather than invoking a method in a v-for (which I guarantee will become problematic for you in the future), you should declare a computed list that contains (or does not contain) the items you want rendered, something like this:
data(){
return {
unreadIds: []
}
},
computed:{
user(){
return this.$store.getters.user;
},
NewFriends() {
return this.user.friends.filter(friend => this.unreadIds.includes(friend.id));
}
}
Your markup would then just be:
<ul>
<li v-for="friend in NewFriends">
<span > ... </span>
</li>
</ul>
And Vue's reactivity system would handle any changes to data dependencies for NewFriends.
You don't want to ever use method calls in a template because method calls are only guaranteed to be invoked once (the same applies to functions that come from computed methods...).
If you find yourself trying to trigger re-renders and dependency checks manually, you will want to re-think your design.
ETA: The only time you will ever want to invoke a function of any kind in a template is to respond to events.
I'll have a stab at it - someone posted this earlier and deleted it. You need checkIfNew() to be a computed not a method for reactivity in the template.
Since you need to pass in the id, the computed needs to return a function.
data(){
return {
unreadIds: []
}
},
computed:{
user(){
return this.$store.getters.user;
},
checkIfNew(){
return (id) => {
return this.unreadIds.includes(id);
}
}
}
As David Weldon says, you should ideally change the array immutably - and probably why ohgodwhy asked the original question.
I'm new to vue.js and I'm trying to use vuex. Here is my issue:
I have a list of articles (which is a component) linked with a store with v-for="article in articles and a computed property:
computed: {
articles() {
return this.$store.state.articles
}
}
So here is the data in my store:
state: {
articles: [{
title: "Article 1",
id: 1,
description: "Article 1",
}, {
title: "Article 2",
id: 2,
description: "Article 2",
}
}]
}
When I click on an article, I want it to redirect to the article page template (which is a component) with <router-link :to="{path: '/article/'+article.id}"></router-link>.
What I'm trying to do is bind the data of the correct article in the articlePage template.
The issue is that if I apply the same computed property to my articlePage.vue component with a v-for, I will display all of the article on the same page. I would like to display only the matching id component.
How can I do that?
Thank you for your time :)
From your comments I understand that you use vue-router module
So in your routes.js (or structure ) your must have something like this
const router = new VueRouter({
routes: [
{ path: '/articles', component: articlesPage },
{ path: '/article/:id', component: articlePage }
]
})
Then in your articlePage component you can extract ":id" like this:
this.$route.params.id
because vue-router gives you access to the object $route with methods and properties
Check more here https://router.vuejs.org/guide/essentials/dynamic-matching.html
then you can use it to search the articles array and find the data and present them
e.x.
computed:{
selectedArticle(){
var article_id = this.$route.params.id;
var articles = this.$store.state.articles;
var article = null;
for(var a=0;a<articles.length;a++){
if(articles[a].id == article_id ){
article = articles[a];
break;
}
}
return article;
}
}
Suppose I have an array feedsArray, the example value may look like this:
this.feedsArray = [
{
id: 1,
type: 'Comment',
value: 'How are you today ?'
},
{
id: 2,
type: 'Meet',
name: 'Daily sync up'
}
]
Suppose I have registered two components: Comment and Meet, Each component has a prop setting as the following:
props: {
feed: Object
}
and the main component has the following definition:
<component v-for="feed in feedsArray" :feed="feed" :key="feed.id" :is="feed.type"></component>
As you can see, it uses is property to select different component. My question is, how to detect feed object change in the child component ? Like when I set
this.feedsArray[0] = {
id: 1,
type: 'Comment',
value: 'I am not ok'
}
How can the Comment component detect the changes ? I tried to add a watcher definition in the child component like the following:
watch: {
feed: {
handler (val) {
console.log('this feed is changed')
},
deep: true
}
},
But it doesn't work here. Anyone know how to solve this ?
Do not assign directly to an array using index - use splice() instead, otherwise JavaScript can not detect that you have changed the array.
If you want to change only the value of an already existing key of an object - then simply update it e.g. this.feeds[0].value = 'I am not okay any more';
This works for existing keys only - otherwise you have to use this.$set(this.feeds[0], 'value', 'I am not okay any more');
I have an list of objects in firebase called meals.
In this component I want to show information on only one meal so I pass an id field as a prop and I want to get only that meal from firebase.
This is what I tried. It didn't work because this.id was undefined:
import db from '#/firebase'
export default {
name: 'meal',
props: {
id: {
type: String,
required: true
}
},
firebase: {
meals: db.ref('meals').child(this.id)
}
}
Did I do something wrong or does the firebase call happens before the props are initialized?
EDIT:
I managed to do it using the created hook but it looks pretty bad. Is there any other way?
created() {
this.$bindAsObject('meal', db.ref('meals').child(this.id))
}
This one burned me pretty hard. You have to use the firebase function AND create a reference to your variable like this:
import db from '#/firebase'
export default {
name: 'meal',
props: {
id: {
type: String,
required: true
}
},
firebase() {
const id = this.$props.id // Pass the reference instead of the prop directly
return {
meals: db.ref('meals').child(id)
}
}
}
According to the official doc of VueFire, your way to use the creation hook is exactly correct. Firebase basically works in a different lifecycle with Vue.js, so you need to use that doller syntax provided VueFire.