Vue 3 Composition API variable undefined after cloning it from props

30 Views Asked by At

I have main component that have some code and tables in it and among it it contains modal that I'm setting to visible depending on boolean.

Main component have this modal included:

 <ConfirmPaymentModal
    ref="confirmPaymentModal"
    :invoice="markAsPaidInvoice"
    :max-amount="maxAmount"
    :is-dialog-visible="markAsPaidVisible"
    @update:is-dialog-visible="markAsPaidVisible = $event"
    @submit="markAsPaidModal(markAsPaidInvoice.id, $event, false )"
  />

In my ConfirmPaymentModal I have this as setup:

<script setup>
import { formatThousands } from '@layouts/utils'

const props = defineProps({
    isDialogVisible: {
        type: Boolean,
        required: true,
    },
    invoice : {
        type: Object,
        required: true,
    },
    maxAmount : {
        type: Number,
        required: true,
    },
})

const emit = defineEmits([
    'submit',
    'update:isDialogVisible',
])

const totalAmount = ref(props.invoice.total_amount)
</script>

Idea is that I can set maximum value for :

<VTextField
    v-model="props.invoice.total_amount"
    type="number"
    density="compact"
    :label="'Total ' + props.invoice.currency"
 />

I was about to do it within watcher:

watch(() => props.invoice.total_amount, newValue => {
    if (newValue > props.maxAmount) {
        props.invoice.total_amount = props.maxAmount
    }
})

Now, for some reason, totalAmount is undefined always. I need to clone value somehow because I can't manipulate over props.invoice.total_amount. I can't change its value in watcher. Can you please help me understand how this should be done?

Thanks!

Note: This ended up working but I have a feeling that I should never change props values. Am I wrong?

1

There are 1 best solutions below

0
Boussadjra Brahim On

You want to make a two-way binding, you can achieve by binding the text field to a writable computed property that gets the prop and when it's set emits the new value :

<script setup>
import { formatThousands } from "@layouts/utils";

const props = defineProps({
  isDialogVisible: {
    type: Boolean,
    required: true,
  },
  invoice: {
    type: Object,
    required: true,
  },
  maxAmount: {
    type: Number,
    required: true,
  },
});

const emit = defineEmits(["submit", "update:isDialogVisible", "update:invoice"]);

const totalAmount = computed({
  get() {
    return props.invoice.totalAmount;
  },
  set(value) {
    emit("update:invoice", {
      ...props.invoice,
      totalAmount: value,
    });
  },
});
</script>
<template>
  <VTextField v-model="totalAmount" type="number" density="compact" :label="'Total ' + props.invoice.currency" />
</template>

In parent add v-model:invoice="markAsPaidInvoice":

<ConfirmPaymentModal
    ref="confirmPaymentModal"
    v-model:invoice="markAsPaidInvoice"
    :max-amount="maxAmount"
    :is-dialog-visible="markAsPaidVisible"
    @update:is-dialog-visible="markAsPaidVisible = $event"
    @submit="markAsPaidModal(markAsPaidInvoice.id, $event, false )"
  />