I have a use case where I may need to pass an object props as props to a child component.
Initially, I had a form and a table contained within a component. This form would take input, an asynchronous request would be performed and the table would be rendered for the user to make a selection. The user could then hit a button and have the table hidden and the form brought back up so parameters could be re-entered. Since the content of the form was dependent on the state of its parent, the last search parameters were still in the form.
The issue arose when I refactored the component to make both the form and the table sub-components of the parent. Now the form would $emit the event to its parent, which would perform the asynchronous operation and pass the result as props to the table. This worked fine, but when the user hits the "return to form" button, the form is re-rendered, thus resetting its state to the initial value.
I tried storing the content of the form in the parent and passing it back to the form as props but that led to the problem of initially setting the values. I didn't want to mutate the props directly, so I tried this approach:
FormContainer.vue
<template> <div v-if="formShown"> <form-component :initialValues="formValues" @formSubmitted="displayResults"></form-component> </div> <div v-if="tableShown"> <table-component :results="fetchedResults" @returnToForm="returnToForm"></table-component> </div> </template> <script> export default { data(){ return{ formShown: true, tableShown: false, formValues:{ address1: '', address2: '', address3: '', country: '' }, fetchedResults: [] } }, methods:{ async displayResults(){ this.fetchedResults = await someAsynchronousCall(); this.formShown = false; this.tableShown = true; }, returnToForm(){ this.tableShown = false; this.formShown = true; } } } </script> FormComponent.vue
<template> <!--Some form fields here, bound to data(){}, ommitted for brevity--> </template> <script> export default{ props:['initialValues'], data(){ return{ //Original structure /*selectedAddress:{ address1: '', address2: '', address3: '', country: '' }*/ selectedAddress: JSON.parse(JSON.stringify(this.initialValues)) //Object.assign({}, this.initialValues) //tried both of these for deep copy } } } </script> The problem with this was that when the component was initially created, it is getting passed an empty object (or rather an object with only empty properties, I assume they amount to the same thing), which means my data properties are getting initialized as undefined.
I then tried using a ternary to initialize the data object with the values in the props or an empty string, like this:
data(){ return{ selectedAddress: { address1: this.initialValues.address1 ? this.initialValues.address1 :'', address2: this.initialValues.address2 ? this.initialValues.address2 :'', address3: this.initialValues.address3 ? this.initialValues.address3 :'', country:this.initialValues.country ? this.initialValues.country :'' } } }, But this throws errors saying that selectedAddress is not defined on the instance but is referenced during render. I'm guessing this means that using a ternary to initialize props is wrong.
I then tried to check for props in the mounted(){} lifecycle hook and set the data properties there, like this:
mounted(){ if(!this.initialValues || _.isEmpty(this.initialValues)){ Logger.info(`no props passed, no setup needed`); return; } if(this.initalValues.address1){ Logger.info(`setting address 1`); this.selectedAddress.address1 = this.initalValues.address1; } if(this.initialValues.address2){ Logger.info(`setting address 2`); this.selectedAddress.address2 = this.initalValues.address2; } if(this.initialValues.address3){ Logger.info(`setting address 3`); this.selectedAddress.address3 = this.initalValues.address2; } if(this.initialValues.country){ Logger.info(`setting country`); this.selectedAddress.country = this.initalValues.country; } } This works for the first run of the form, but this.initialValues is always undefined when the component is mounted. I have inspected the component state and found that initialValues does exist, just not during the mounted life cycle hook. This approach felt 'hacky' anyway.
I'm not really sure where to go from here. I could commit the form data to the store and get it again if the form is remounted, but I don't feel like committing something that should only exist while its parent is mounted is the right approach.
Can anyone guide me to a more Vue way of achieving this?