Reputation: 121
I'm trying to access a component's data in my root Vue instance. What I'm trying to accomplish is, you click on a button and a set of inputs appear. Each set of inputs is its own object in localStorage with the keys id, bags, and amount. When I update a set of inputs, their values should update in localStorage. I'm having trouble figuring out how to access my component's data for bags and amount. What I think I'm supposed to be using is props because that's what I used in React. However, I'm not too comfortable with using them yet in Vue.
Here is what I have so far:
var STORAGE_KEY = "orders";
var orderStorage = {
fetch: function() {
var orders = JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
orders.forEach(function(order, index) {
order.id = index;
});
orderStorage.uid = orders.length;
return orders;
},
save: function(orders) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(orders));
}
};
Component
Vue.component('order', {
template: `
<div>
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<input class="input" id="bags" v-model="bags" type="number" placeholder="Bags">
<input class="input" id="amount" v-model="amount" type="number" placeholder="Amount">
<button @click="$emit('remove')">Remove</button>
</div>
`,
props: {
bags: this.bags, // I must be doing this wrong
amount: this.amount
},
data() {
return {
bags: null,
amount: null,
selected: null,
options: [{
text: "Product (25kg)",
value: 25
}, {
text: "Product (50kg)",
value: 50
}, {
text: "Product (75kg)",
value: 75
}]
}
},
watch: {
bags(newValue) {
this.amount = newValue * this.selected;
},
amount(newValue) {
this.bags = newValue / this.selected;
}
}
});
Root Vue Instance
new Vue({
el: '#app',
data: {
orders: orderStorage.fetch(),
bags: null,
amount: null,
},
watch: {
orders: {
handler: function(orders) {
orderStorage.save(orders);
},
deep: true
}
},
methods: {
addProduct: function() {
this.orders.push({
id: orderStorage.uid++,
bags: this.bags, // component.bags ?
amount: this.amount// component.amount?
});
console.log(localStorage);
}
}
});
HTML
<div id="app">
<button @click="addProduct">Add</button>
<div class="orders">
<order v-for="(order, index) in orders" :id="index" :key="order" @remove="orders.splice(index, 1)" :bags="bags" :amount="amount"></order>
</div>
</div>
Example: https://codepen.io/anon/pen/YVwxpz?editors=1010
Upvotes: 3
Views: 5311
Reputation: 43881
If props are confusing you, be sure to read about Composing Components, which talks about the "props down, events up" convention.
Props are data that is controlled from outside the component. The component should not make modifications to its props (as you are doing in your watch
, and as you are implicitly doing by using them with v-model
).
You probably want to make component data
items that you initialize from the props (give them names differerent from the props names!) You can then manipulate those values.
Since you want the parent to be aware of changes to the values, you should emit events when changes happen. The parent can catch those events and take appropriate action, like saving them to the order object and to localStorage.
Upvotes: 1
Reputation: 82459
Typically, you do not want to reach into a component to get its data. You want the component to $emit
it. Vue is, generally, props down, events up.
I've modified your order component to support v-model
. This allows the changes to the component to be reflected in the parent automatically.
Vue.component('order', {
props:["value"],
template: `
<div>
<select v-model="order.value">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<input class="input" id="bags" v-model="order.bags" type="number" @input="onBags" placeholder="Bags">
<input class="input" id="amount" v-model="order.volume" type="number" @input="onVolume" placeholder="Amount">
<button @click="$emit('remove')">Remove</button>
</div>
`,
data() {
return {
options: [{
text: "Product (25kg)",
value: 25
}, {
text: "Product (50kg)",
value: 50
}, {
text: "Product (75kg)",
value: 75
}],
order: this.value
}
},
methods: {
onBags() {
this.order.volume = this.order.bags * this.order.value;
this.$emit("input", this.order)
},
onVolume() {
this.order.bags = this.order.volume / this.order.value;
this.$emit("input", this.order)
}
}
})
new Vue({
el: '#app',
data: {
orders: orderStorage.fetch(),
},
watch: {
orders: {
handler: orders => orderStorage.save(orders),
deep: true
}
},
methods: {
addProduct: function(order) {
this.orders.push({
id: orderStorage.uid++,
bags: 0,
volume: 0,
value: 25
});
}
}
});
Doing this, you don't have to pass bags
and amount
, you just pass the order. Then, when the order changes, the modified order is emitted.
A working example is here.
Additionally, I added the value
property to your order
objects. If you don't save that, then you can't properly calculate the changes to bags and volume.
Upvotes: 2