简体   繁体   中英

Vue.js: using computed property in method returns undefined error

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
        }
    }
]

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>

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM