How can I capture the data from a component input?

148 Views Asked by At

I have a component call NewIngredientInput, that I use on my newrecipe page. The component is made up of 3 inputs amount, unit and name. I'm using Nuxt 3, handling the forms with Formkit and consuming and external API.

When I send the data from the form to the endpoint for posting, its only capturing the information from the input amount, for the other two inputs (unit and name) it sends an empty string so the POST request succeeds. I specify that for the other inputs that are not inside the NewIngredientInput component, the information is successfully captured.

Here is my code for newrecipe page:

<template>
  <div class="container">
    <UserMenu />
    <SideNavUser />
    <div class="new-recipe-container">
      <div class="title">Crear Receta</div>
      <div class="form">
        <FormKit type="form" @submit="createRecipe">
          <FormKit
            v-model="recipeData.name"
            type="text"
            name="name"
            label="Título de la Receta"
            validation="required"
          />
          <div
            class="new-ingredient"
            v-for="(row, index) in inputRows"
            :key="index"
          >
            <NewIngredientInput
              v-model="row.amount"
              v-model:unit_measure="row.unit_measure"
              v-model:name="row.name"
              :showRemoveButton="index > 0"
              @removeRow="removeRow(index)"
            />
            <button @click="addRow" class="add">+</button>
          </div>
          <FormKit
            v-model="recipeData.description"
            type="textarea"
            rows="10"
            name="description"
            label="Instrucciones"
            placeholder="Escribe oraciones o párrafos."
            validation="required"
          />
          <div class="triple">
            <FormKit
              v-model="recipeData.region"
              type="text"
              name="region"
              label="Región"
              validation="required"
            />
            <FormKit
              v-model="recipeData.keywords"
              type="text"
              name="keywords"
              label="Palabras Clave"
              placeholder="pollo, ajo, ..."
              help="Ingresa palabras claves separadas por comas."
              validation="required"
            />
            <div class="photo">
              <FormKit
                type="file"
                name="photo"
                accept=".jpg,.png"
                label="Imagen"
                help="Solo puedes incluir imagenes con extensión .jpg o .png"
                validation="required"
              />
            </div>
          </div>
          <FormKit type="submit" label="Crear" />
        </FormKit>
      </div>
    </div>
  </div>
</template>

<script>
import NewIngredientInput from "../components/NewIngredientInput.vue";
import axios from "axios";

export default {
  components: {
    NewIngredientInput,
  },
  data() {
    return {
      inputRows: [{ amount: "", unit_measure: "", name: "" }],
      recipeData: {
        name: "",
        description: "",
        region: "",
        keywords: "",
      },
    };
  },
  methods: {
    addRow() {
      this.inputRows.push({ amount: "", unit_measure: "", name: "" });
    },
    removeRow(index) {
      this.inputRows.splice(index, 1);
    },

    async createRecipe() {
      try {
        const photoInput = document.querySelector('input[name="photo"]');

        if (photoInput && photoInput.files && photoInput.files.length > 0) {
          const photoForm = new FormData();
          photoForm.append("photo", photoInput.files[0]);

          const imageResponse = await axios.post(
            "API endpoint for upload newImage",
            photoForm,
            {
              headers: {
                "Content-Type": `multipart/form-data; boundary=${photoForm._boundary}`,
              },
            }
          );

          const ingredient = this.inputRows.map((row) => {
            return {
              amount: row.amount.amount,
              unit: row.unit_measure,
              name: row.name,
            };
          });

          const recipeForm = {
            name: this.recipeData.name,
            ingredients: ingredient,
            description: this.recipeData.description,
            region: this.recipeData.region,
            keywords: this.recipeData.keywords,
            photo: imageResponse.data,
          };

          console.log(recipeForm);

          const recipeResponse = await axios.post(
            "API endpoint for upload the entire recipe",
            recipeForm
          );

          console.log("Receta creada exitosamente: ", recipeResponse.data);
        } else {
          console.error("No se seleccionó ninguna imagen");
        }
      } catch (error) {
        console.error(error);
      }
    },
  },
};
</script>

If I don't use v-model:unit_measure="row.unit_measure" and v-model:name="row.name" on NewIngredientInput, when I add a row for a new ingredient it inherits the information from the old one and is overwritten, resulting in a single row instead of an array with several ingredients.

I also attached the script of the NewIngredientInput component for more context:

<template>
  <FormKit type="form" name="ingredients">
    <div class="newIngredient">
      <FormKit
        v-model="ingredient.amount"
        type="number"
        name="amount"
        id="amount"
        label="Cantidad"
        min="1"
        validation="required"
      />
      <FormKit
        v-model="ingredient.unit_measure"
        type="text"
        name="unit"
        id="unit"
        label="Unidad"
        validation="required"
      />
      <div class="ingredient">
        <FormKit
          v-model="ingredient.name"
          type="text"
          name="name"
          id="name"
          label="Ingrediente"
          validation="required"
        />
      </div>
      <button class="remove" @click="removeRow" v-show="showRemoveButton">
        -
      </button>
    </div>
  </FormKit>
</template>

<script>
export default {
  props: {
    showRemoveButton: {
      type: Boolean,
      default: false,
    },

    // Pass the ingredient object as a prop
    ingredientValue: Object,
  },
  data() {
    return {
      ingredient: { amount: "", unit_measure: "", name: "" },
    };
  },
  watch: {
    // When the prop value (ingredient object) changes, update the local ingredient data
    ingredientValue: {
      handler(newValue) {
        this.ingredient.amount = newValue.amount;
        this.ingredient.unit_measure = newValue.unit_measure;
        this.ingredient.name = newValue.name;
      },
      deep: true,
    },
  },
  methods: {
    removeRow() {
      this.$emit("removeRow");
    },
  },
};
</script>
1

There are 1 best solutions below

0
On

If I understand the problem correctly, the problem is how you're using v-model on your NewIngredientInput component. It should bind to a single model, but you're binding to multiple models (amount, unit_measure, name). Here is the simplified solution:

Change your NewIngredientInput in newrecipe page like this:

<NewIngredientInput
  :value="row"
  :showRemoveButton="index > 0"
  @removeRow="removeRow(index)"
/>

And:

<template>
  <!-- Your existing template code -->
</template>

<script>
export default {
  props: {
    showRemoveButton: Boolean,
    value: { type: Object, required: true },
  },
  data() {
    return { ingredient: { ...this.value } };
  },
  watch: {
    ingredient: {
      handler() { this.$emit('update', this.ingredient); },
      deep: true
    },
    value: {
      handler(newValue) { this.ingredient = { ...newValue }; },
      deep: true
    }
  },
  methods: {
    removeRow() { this.$emit("removeRow"); },
  },
};
</script>

Hope this helps!