Reputation: 2059
I'm working on an application that uses Vue.js (currently v 2.6.11), and as the application grows I see a pattern emerge quite often: I want to abstract away some functionality, so I extract a new component, but when I copy some code in the mounted
hook from the parent to the child, some of the data that were previously available are now undefined. Sometimes the data comes from a prop, other times it comes from a Vuex store.
I think the issue comes from the fact that, contrarily to what I initially thought, inner components are initialized before outer components.
Here is an example:
<template>
<div class="options-container">
<features-list />
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import FeaturesList from '../features/FeaturesList.vue';
export default {
name: 'ProductComponent',
components: {
FeaturesList,
},
computed: {
...mapGetters('product', [
'userSelection',
]),
},
mounted() {
// here I can call the methods which rely on UserSelections
// the variable UserSelections has already a value here
const params = this.getTemplateCollectionParams();
this.getTemplateAssets(params);
},
};
</script>
<template>
<div class="options">
<template v-for="key in sortedFeaturesLayout">
<feature-base-component
:option="options[key]"
:key="key"
v-on="$listeners"
/>
</template>
</div>
</template>
<script>
import FeatureBaseComponent from './FeatureBaseComponent.vue';
import { mapGetters } from 'vuex';
export default {
name: 'FeaturesList',
components: {
FeatureBaseComponent,
},
computed: {
...mapGetters('product', [
'userSelection',
]),
},
methods: {
getTemplateCollectionParams() {
// ...
},
getTemplateAssets(params) {
// ...
},
},
mounted() {
// here I have an error because `userSelection` is still empty
const params = this.getTemplateCollectionParams();
this.getTemplateAssets(params);
},
};
</script>
What I usually do is create a watcher on the value and then call the function on the update of the watcher. Since the original code was executing only once I want to check that this method is called only once too, so add a boolean that checks if this code has already been executed.
<template>
<div class="options">
<template v-for="key in sortedFeaturesLayout">
<feature-base-component
:option="options[key]"
:key="key"
v-on="$listeners"
/>
</template>
</div>
</template>
<script>
import FeatureBaseComponent from './FeatureBaseComponent.vue';
import { mapGetters } from 'vuex';
export default {
name: 'FeaturesList',
components: {
FeatureBaseComponent,
},
data() {
return {
isFirstLoad: true,
};
},
computed: {
...mapGetters('product', [
'userSelection',
]),
},
methods: {
getTemplateCollectionParams() {
// ...
},
getTemplateAssets(params) {
// ...
},
},
watch: {
userSelection() {
// get gallery
if (this.isFirstLoad) {
// here I need the getter userSelection which is empty on `mounted`
const params = this.getTemplateCollectionParams();
this.getTemplateAssets(params);
this.isFirstLoad = false;
}
},
},
};
</script>
I think that what I'm doing is not completely wrong. However, I wonder if there is a cleaner way of executing a method only the first time a prop has a value because sometimes I end up with plenty of booleans, which are only used for this purpose.
Upvotes: 3
Views: 1228
Reputation: 138226
You could alternatively use vm.$watch
, which returns an unwatch
function that can be called to stop subsequent callbacks:
const unwatch = this.$watch('userSelection', userSelection => {
//...
// stop watching
unwatch()
})
This has the benefits of not requiring an extra flag to check whether the watch handler has already been called, and the watcher is only ever called once.
Upvotes: 1