I have a simple app with a common stack :
The Vue app fetch data from the backend using an action of the Vuex store library as so :
// store/store.js
import Vue from 'vue';
import Vuex from 'vuex';
import * as MutationTypes from '@/store/mutation-types';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
investment: {},
},
mutations: {
[MutationTypes.SET_INVESTMENT_SHOW](state, investment) {
state.investment = investment;
},
},
actions: {
fetchInvestment({ commit }, id) {
InvestmentsApi.get(id).then((response) => {
commit(MutationTypes.SET_INVESTMENT_SHOW, response.data);
});
},
},
getters: {
participation: state =>
state.investment.included[0],
},
});
The action is called in the created lifecycle hook of my component as so :
// components/Investment.vue
import { mapActions, mapGetters } from 'vuex';
export default {
name: 'Investment',
computed: {
...mapState(['investment']),
...mapGetters(['participation']),
},
created() {
this.fetchData(this.$route.params.id);
},
methods: mapActions({
fetchData: 'fetchInvestment',
}),
};
There is a problem in the code I've written above, I actually use the computed value 'participation' in my template like this :
<BaseTitleGroup
:subtitle="participation.attributes.name"
title="Investissements"
/>
And of course, because I use participation at the time the component renders itself, I get this error from the getter method :
Error in render: "TypeError: Cannot read property '0' of undefined"
found in
---> <InvestmentSummary> at src/views/InvestmentSummary.vue
<App> at src/App.vue
<Root>
I think there are several solutions to solve this problem and I'm wondering which one is best practice or if there is a better one.
I'm looking for the best solution to deal with this pattern, even if it's one of the above, I'm just not sure which one would be the best. Thanks a lot for reading ! Also, I use vue framework but I think it is more of a general question about modern javascript framework handling of async data and rendering.
Sorry for the long post, here is a potato ! (Oops, not on 9gag ;) )
In Angular, there is the Elvis (safe navigation) operator, which is a concise way to handle reactive data that eventually arrives.
If it was available in the Vue template compiler, your template would look like this:
<BaseTitleGroup
:subtitle="participation?.attributes?.name"
title="Investissements"
/>
However, Evan You says that it sounds like a code smell.
Your model/state should be as predictable as possible.
Trying to expand that comment into your context, I think it means that your template knows more about your data structure than your store.
template
"participation.attributes.name"
which equates to:
state.investment.included[0].attributes.name
store
state: {
investment: {},
},
Since the getter is there to serve the Component (and it's template), I would opt for enhancing the getter.
getters: {
participation_name: state => {
return
(state.investment.included
&& state.investment.included.length
&& state.investment[0]
&& state.investment[0].attributes
&& state.investment[0].attributes.name)
|| null;
},
<BaseTitleGroup
:subtitle="participation_name"
title="Investissements"
/>
But if you wanted elvis functionality, you could provide it in a mixin.
var myMixin = {
computed: {
elvis: {
get: function() {
return (known, unknown) => {
// use regex split to handle both properties and array indexing
const paths = unknown.split(/[\.(\[.+\])]+/);
let ref = known
paths.forEach(path => { if (ref) ref = ref[path] });
return ref;
}
}
},
}
}
export default {
name: 'Investment',
...
mixins: [myMixin],
computed: {
...mapState(['investment']),
participation_name() {
return this.elvis(this.investment, 'included[0].attributes.name')
}
},
...
};
I don't think there is the best solution, just choose one and use it everywhere instead of mixing them all.
v-if
however might be better in case you want to render data from a nested property - v-if="object.some.nested.property v-for="el in object.some.nested.property"
will work but pre-defining object = {}
will not (it will throw an error that some
is undefined and you are trying to access it).
I wouldn't put any fake data like in your example, but you can use ES6 Classes to define default objects and set them as your default values. This also fixes the above pre-defining problem as long as your class object has a proper structure (and it's also syntatically transparent and easy to understand).
As for the third option - giving empty data to getters doesn't have to be complicated - just change your getter to:
getters: {
participation: state =>
state.investment.included[0] || new DefaultParticipationObject() // I don't know what's in included array
},
This uses state.investment.included[0]
if it's defined and a default object otherwise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With