I'm trying to learn Vue and want to build a parent component of shopping list (my training excercise). The parent will manage the state and it contains two child components: 1) the table with products and prices for each product 2) the form to add a new item.
I have the following ShoppingListComponent.vue
:
<template>
<table>
<thead>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products" :key="product.id">
<td>{{ product.name }}</td>
<td>{{ product.price }}</td>
</tr>
</tbody>
</table>
</template>
<script setup lang="ts">
import type { Product } from './models';
interface Props {
products: Product[];
}
defineProps<Props>();
</script>
The Product interface is simply:
export interface Product {
id: number;
name: string;
price: number;
}
The AddProductFrom.vue
:
<!-- I use quasar ->
<template>
<div class="row q-gutter-sm">
<q-input filled v-model="product.name" label="Name" />
<q-input filled v-model="product.price" label="Price />
<q-btn color="black" text-color="white" label="Add" @click="$emit('addRow', product)" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { type Product } from './models';
const product = ref<Product>({
id: 1,
name: '',
price: 0,
});
defineEmits<{
addRow: [item: Product];
}>();
</script>
This "works" and adds a row to the table, but there is still some sort of reference because when I add a product and then change the contents of the form the data in the table changes as well.
What I want is to be able to add a product, clear the form and add a new product (what you would expect). How can I solve this?
When you updated the form fields after emitting, it modified the object that was already in the parent's products array (since it was the same reference). By creating a new object on emit and resetting the form, you ensure that each added product is a distinct object, and the form starts fresh each time.
AddProductForm.vue
<template>
<div class="row q-gutter-sm">
<q-input filled v-model="product.name" label="Name" />
<q-input filled v-model="product.price" label="Price" />
<q-btn color="black" text-color="white" label="Add" @click="addProduct" />
</div>
</template>
<script setup lang="ts">
const emit = defineEmits<{
addRow: [item: Product];
}>();
const product = ref<Product>({
id: 0,
name: "",
price: 0,
});
function addProduct() {
emit("addRow", { ...product.value });
product.value = {
id: 0,
name: "",
price: 0,
};
}
</script>
ShoppingListComponent.vue
<template>
<div>
<ShoppingListComponent :products="products" />
<AddProductForm @addRow="addProduct" />
</div>
</template>
<script setup lang="ts">
const products = ref<Product[]>([
{ id: 1, name: "Apple", price: 0.5 },
{ id: 2, name: "Banana", price: 0.3 },
]);
function addProduct(newProduct: Product) {
const id = products.value.length + 1;
products.value.push({ ...newProduct, id });
}
</script>