1

I have a computed property that gets the state from vuex. Inside vuex the state is set using axios to get some data from my api. My issue is that when I try use this computed property inside my methods I get an undefined error. This is because I try use the data before it has been set in the vuex store. So how do I make sure the boardColumnData is set before I try using it in my methods?

errors:

Error in mounted hook: "TypeError: Cannot read property 'total' of undefined"
TypeError: Cannot read property 'total' of undefined

AppCharts.vue

<template>
    <div id="chart_section">
        <div id="charts" v-if="boardColumnData">
            <DoughnutChart :chart-data="datacollection" :options="chartOptions" class="chart"></DoughnutChart>

        <button v-on:click="fillData">Fill Data</button>
    </div>
</template>


<script>
import DoughnutChart from './DoughnutChart';
import { mapGetters } from 'vuex';

export default {
    components: {
        DoughnutChart
    },
    computed: {
        ...mapGetters(['boardColumnData']),
    },
    data() {
        return {
            datacollection: null,
            chartOptions: null
        }
    },
    mounted() {
        this.fillData();
    },
    methods: {
        fillData() {
            this.datacollection = {
                datasets: [{
                    data: [this.boardColumnData[0].total.$numberDecimal, this.boardColumnData[1].total.$numberDecimal, this.boardColumnData[2].total.$numberDecimal, this.boardColumnData[3].total.$numberDecimal],
                    backgroundColor: [
                        '#83dd1a', 
                        '#d5d814',
                        '#fdab2f',
                        '#1ad4dd'
                    ],
                    borderColor: [
                        '#83dd1a', 
                        '#d5d814',
                        '#fdab2f',
                        '#1ad4dd'
                    ],
                }]
            };

            this.chartOptions = {
                responsive: true,
                maintainAspectRatio: false,
            };
        }
    }
}
</script>

DoughtnutChart.vue

<script>
    import { Doughnut, mixins } from 'vue-chartjs';
    const { reactiveProp } = mixins;

    export default {
        extends: Doughnut,
        mixins: [reactiveProp],
        props: ['chartData', 'options'],
        mounted () {
            this.renderChart(this.chartdata, this.options)
        }
    }
</script>

vuex store:

import axios from 'axios';

const state = {
    defaultPosts: [],
    boardPosts: [],
    boardColumnData: [],
};

const getters = {
    boardColumnData: state => state.boardColumnData,
};

const actions = {
    getAllBoardPostData: ({commit}) => {
        function getBoardColumns() {
            return axios.get('http://localhost:5000/api/summary-board/columns');
        }

        function getBoardPosts() {
            return axios.get('http://localhost:5000/api/summary-board/posts');
        }

        axios.all([getBoardColumns(), getBoardPosts()])
            .then(axios.spread((columnData, postData) => {
                let rawPosts = postData.data;
                let columns = columnData.data;
                let posts = Array.from({length: columns.length}, () => []);

                rawPosts.forEach((post) => {
                    // If column index matches post column index value
                    if(posts[post.column_index]){
                        posts[post.column_index].push(post);
                    }
                });

                columns.forEach((column, index) => {
                    let columnTotal = 0;

                    posts[index].forEach((post) => {
                        columnTotal += post.annual_value;
                    });

                    column.total.$numberDecimal = columnTotal;
                });

                commit('setBoardColumns', columns);
                commit('setBoardPosts', posts);
                commit('setDefaultPosts', posts);
            }))
            .catch(error => console.log(error));
    }
};

const mutations = {
    setDefaultPosts: (state, payload) => {
        state.defaultPosts = payload;
    },
    setBoardPosts: (state, payload) => {
        state.boardPosts = payload;
    },
    setBoardColumns: (state, payload) => {
        state.boardColumnData = payload;
    }
};

export default {
    state,
    getters,
    actions,
    mutations
};

boardColumnData looks like this:

[
    {
        "name": "Opportunities",
        "percentage": {
            "$numberDecimal": "0"
        },
        "total": {
            "$numberDecimal": 70269
        }
    },
    {
        "name": "Prospects",
        "percentage": {
            "$numberDecimal": "0.25"
        },
        "total": {
            "$numberDecimal": 0
        }
    },
    {
        "name": "Proposals",
        "percentage": {
            "$numberDecimal": "0.5"
        },
        "total": {
            "$numberDecimal": 5376
        }
    },
    {
        "name": "Presentations",
        "percentage": {
            "$numberDecimal": "0.75"
        },
        "total": {
            "$numberDecimal": 21480
        }
    },
    {
        "name": "Won",
        "percentage": {
            "$numberDecimal": "1"
        },
        "total": {
            "$numberDecimal": 0
        }
    },
    {
        "name": "Lost",
        "percentage": {
            "$numberDecimal": "1"
        },
        "total": {
            "$numberDecimal": 0
        }
    },
    {
        "name": "No Opportunity",
        "percentage": {
            "$numberDecimal": "1"
        },
        "total": {
            "$numberDecimal": 0
        }
    }
]
8
  • is boardColumnData being fetched from APIs and set inside the store? could you please provide store's relevant code part? Commented Apr 21, 2020 at 17:19
  • @CapitanFindus I added my vuex store module. Commented Apr 21, 2020 at 17:25
  • where are you calling getAllBoardPostData from the store? what about returning a promise from the store, so you can be sure you've already commited setBoardColumns Commented Apr 21, 2020 at 17:28
  • I call getAllBoardPostData in another component after the AppCharts.vue component on the same page using this.$store.dispatch('getAllBoardPostData'); inside created() . I tried calling it from the AppCharts.vue component but it didn't change anything. I'll have to look into promises for me to try implement them Commented Apr 21, 2020 at 17:34
  • @CapitanFindus I have just moved it to the outer component inside the created() function. So it should be set before either of the other two components try use it but I still have the same issue Commented Apr 21, 2020 at 17:37

2 Answers 2

1

Vue should be able to handle the reactivity of updating your components once the data arrives in the store, and since you're passing it into your component correctly, I think you just need to make some small adjustments to make the component more reactive. I'd move the datacollection to a computed property, since it's only dependent on the store's boardColumnData, and then could you move your chartOptions to be defined initially, since it's just static data?


export default {

    data: () => ({
      // datacollection: null,  // remove this
      chartOptions: {
        responsive: true,
        maintainAspectRatio: false,
      },
    },

  //...

  computed: {
    ...mapGetters([
      'boardColumnData'
    ]),
    datacollection() {   // Property added to computed properties
      if (this.boardColumnData.length) {   // - check for boardColumnData before using it
        return {
          datasets: [{
            data: [this.boardColumnData[0].total.$numberDecimal, this.boardColumnData[1].total.$numberDecimal, this.boardColumnData[2].total.$numberDecimal, this.boardColumnData[3].total.$numberDecimal],
            backgroundColor: [
              '#83dd1a', 
              '#d5d814',
              '#fdab2f',
              '#1ad4dd'
            ],
            borderColor: [
              '#83dd1a', 
              '#d5d814',
              '#fdab2f',
              '#1ad4dd'
            ],
          }]
        };

      } else {
        return null;
      }
    }, // end dataCollection()
  },

  //... rest of component...

and then in your template, just check for if datacollection is has a value. For instance:

<template>
    <div id="chart_section">
        <div id="charts" v-if="datacollection">
            <DoughnutChart
              :chart-data="datacollection"
              :options="chartOptions"
              class="chart"
            />
        </div>
    </div>
</template>

Sign up to request clarification or add additional context in comments.

Comments

0

You can set the data before the fillData method is called by using async await on the mounted hook.

In AppCharts.vue, inside your mounted() hook call getAllBoardPostData()

`async mounted() {
    await this.$store.dispatch('getAllBoardPostData')
    this.fillData();
},`

This will fetch the data from your api and populate your store when the component is loaded, and before the fillData() method is called

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.