So I have a simple component, let's say Transaction.vue, and inside it there is a child component named TransactionEditModal.vue.
In the Transaction.vue component, I call the method "openTransactionEditModal" by clicking a button. The flow of the method is that I am editing my child property "this.chosenTransactionId" first before opening the modal using "showModal()".
// Transaction.vue
<button ref="editTransactionButton" v-on:click="openTransactionEditModal($event)" >
<i style="pointer-events: none" ></i>
</button>
<TransactionEditModal ref="transactionEditModal" v-bind:transactionId="chosenTransactionId" />
<script>
data: function () {
return {
chosenTransactionId: "",
}
}
methods: {
openTransactionEditModal(event: MouseEvent) {
if (event.currentTarget instanceof HTMLButtonElement) {
this.chosenTransactionId = event.currentTarget.id;
console.log("Chosen Transaction Id is Updated", event.currentTarget.id);
}
var transactionEditModal: any = this.$refs.transactionEditModal; transactionEditModal.initializeExistingValues(this.transactions[6]);
transactionEditModal.showModal();
}
}
</script>
// TransactionEditModal.vue
<script>
props: {
transactionId: String,
},
methods: {
showModal() {
console.log("Child component props should have been updated, is it? ", this.transactionId);
var reportApproveModal: JQuery<HTMLDivElement> = $('#transactionEditModal');
reportApproveModal.modal('show');
},
}
</script>
But why is it that the props is only updated after second click?
Result:
// First Click
Chosen Transaction Id is Updated 5aa1dfc7-4b2f-4dbe-911f-98d70a2624f2 Transaction.vue:365
Child component props should have been updated, is it? TransactionEditModal.vue:36
// Second Click
Chosen Transaction Id is Updated 5aa1dfc7-4b2f-4dbe-911f-98d70a2624f2 Transaction.vue:365
Child component props should have been updated, is it? 5aa1dfc7-4b2f-4dbe-911f-98d70a2624f2 TransactionEditModal.vue:36
UPDATE 1
After using watch functionality in the child component I get this result:
Chosen Transaction Id is Updated 5aa1dfc7-4b2f-4dbe-911f-98d70a2624f2 Transaction.vue:365
Child component props should have been updated, is it? TransactionEditModal.vue:36
Means updated after watch 5aa1dfc7-4b2f-4dbe-911f-98d70a2624f2 TransactionEditModal.vue:42
And I am inferring that it is updated after showModal() is called, which I actually found it to be weird, maybe updating props are asynchronous?
// TransactionEdit.vue
<script>
watch: {
transactionId(newVal, oldVal) {
console.log('Means updated after watch', newVal, oldVal);
},
},
</script>
CodePudding user response:
The reason why the props is only updated after second click is because Vue uses an asynchronous update queue to update the component, which means that when you update the props in the openTransactionEditModal
method, it doesn't take effect immediately. Instead, it is added to the update queue and processed after the current event loop. When you click the button the first time, the props are updated and added to the update queue, but the showModal
method is called before the update is processed, so the child component still has the old props. When you click the button the second time, the update is already processed and the child component now has the updated props.
Using the watch functionality in the child component you can check the updated value of the props and check the exact time it was updated.
You can refactor the code to make sure the child component receives the updated props before the showModal
method is called:
// Transaction.vue
<button v-on:click="openTransactionEditModal($event)" >
<i ></i>
</button>
<TransactionEditModal v-bind:transactionId="chosenTransactionId" @modal-open="showModal" ref="transactionEditModal" />
<script>
data: function () {
return {
chosenTransactionId: "",
}
},
methods: {
openTransactionEditModal(event: MouseEvent) {
if (event.currentTarget instanceof HTMLButtonElement) {
this.chosenTransactionId = event.currentTarget.id;
}
this.$nextTick(() => {
this.$refs.transactionEditModal.initializeExistingValues(this.transactions[6]);
this.$refs.transactionEditModal.$emit('modal-open');
});
},
showModal() {
var reportApproveModal: JQuery<HTMLDivElement> = $('#transactionEditModal');
reportApproveModal.modal('show');
},
}
</script>
// TransactionEditModal.vue
<script>
props: {
transactionId: String,
},
</script>
I have added a custom event 'modal-open'
which is emitted from the parent component after updating the chosenTransactionId and it triggers the showModal()
method. This way, we ensure that the child component has already received the updated props before the modal is opened.
In addition, I have also wrapped the initializeExistingValues()
and $emit('modal-open')
function inside this.$nextTick(() =>{})
to ensure that the value has been updated before calling the function.
Also, I have removed the unnecessary ref attributes and the type casting of HTMLButtonElement.
CodePudding user response:
Are you sure you have made the correct transactionId
reactive?
In Transaction.vue
you have defined a reactive variable called transactionId
.
But then in the code of that component you refer to a variable called chosenTransactionId
.
I suggest you rationalise it to just transactionId
for both.
Or, if you really need two separate variables, then add chosenTransationId
to your data
function in Transaction.vue
.
How about only allowing the modal to exist, once the transactionId
is set?
You could change
<TransactionEditModal ref="transactionEditModal" v-bind:transactionId="chosenTransactionId" />
to
<TransactionEditModal
v-if="chosenTransactionId"
ref="transactionEditModal"
v-bind:transactionId="chosenTransactionId"
/>
This is the more conventional approach. You could even send the initialization information in a v-bind parameter to the child. That would avoid the parent component having to directly call a function within the Modal.
In general it is better to avoid the pattern of calling functions in a child. Instead, make the child exist (with v-if
) only when all the necessary information is available and ready to be passed to it.