You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何先从API获取数据再重新挂载柱状图组件?仪表盘跨组件渲染API数据至BarChart的问题求助

Fixing BarChart Rendering with Async API Data

Hey there, let's fix that BarChart issue you're facing! The problem exactly matches your guess—your BarChart component mounts before the API data arrives, so it initializes with empty arrays, and when the data finally loads, the chart doesn't automatically update to reflect the new props. Here's how to solve this with a couple of solid approaches:

Approach 1: Watch Props and Update the Chart (Dynamic Data Ready)

This method is great if you need the chart to update later (e.g., when filters change or new data comes in). We'll store the Chart instance, listen for prop changes, and update/recreate the chart as needed:

Modified BarChart.vue (Child Component)

<template>
  <div class="card">
    <div class="card-header d-flex justify-content-between align-items-sm-center align-items-start flex-sm-row flex-column"></div>
    <div class="card-body">
      <!-- Use ref instead of getElementById for better component scoping -->
      <canvas ref="chartCanvas" class="bar-chart-ex chartjs" data-height="600"></canvas>
    </div>
  </div>
</template>
<script>
import Chart from "chart.js";

export default {
  name: "BarChart",
  props: {
    activeMembers: {
      type: Array,
      default: () => [] // Use factory function to avoid shared array references
    },
    name: {
      type: Array,
      default: () => []
    },
    totalSentApplications: {
      type: Array,
      default: () => []
    },
  },
  data() {
    return {
      chartInstance: null // Store the chart instance for updates/destruction
    };
  },
  mounted() {
    // Initialize chart only if data exists
    this.initChart();
  },
  watch: {
    // Watch all relevant props for changes (deep watch for array updates)
    activeMembers: {
      handler() {
        this.updateChart();
      },
      deep: true
    },
    name: {
      handler() {
        this.updateChart();
      },
      deep: true
    },
    totalSentApplications: {
      handler() {
        this.updateChart();
      },
      deep: true
    }
  },
  beforeUnmount() {
    // Clean up chart instance to prevent memory leaks
    if (this.chartInstance) {
      this.chartInstance.destroy();
    }
  },
  methods: {
    initChart() {
      // Don't create chart if any data array is empty
      if (!this.name.length || !this.totalSentApplications.length || !this.activeMembers.length) {
        return;
      }

      const ctx = this.$refs.chartCanvas.getContext("2d");
      this.chartInstance = new Chart(ctx, {
        type: "bar",
        data: this.getChartData(),
        options: this.getChartOptions()
      });
    },
    updateChart() {
      if (this.chartInstance) {
        // Update existing chart data and refresh
        this.chartInstance.data = this.getChartData();
        this.chartInstance.update();
      } else {
        // Create chart if it doesn't exist yet
        this.initChart();
      }
    },
    // Extract data and options into reusable methods for clarity
    getChartData() {
      return {
        labels: this.name,
        datasets: [
          {
            label: this.$t("total_sent_applications") || "Total Sent Applications",
            data: this.totalSentApplications,
            backgroundColor: "rgba(71, 183, 132, 0.5)",
            borderColor: "#47b784",
            borderWidth: 1,
          },
          {
            label: this.$t("active_members") || "Active Members",
            data: this.activeMembers,
            backgroundColor: "rgba(255, 255, 0, 0.5)",
            borderColor: "#255255",
            borderWidth: 1,
          },
        ]
      };
    },
    getChartOptions() {
      return {
        barValueSpacing: 10,
        responsive: true,
        lineTension: 1,
        scales: {
          xAxes: [
            {
              label: "chart",
              ticks: {
                beginAtZero: false,
                padding: 5,
              },
            },
          ],
          yAxes: [
            {
              label: "chart",
              ticks: {
                beginAtZero: false,
                padding: 10,
              },
            },
          ],
        },
      };
    }
  }
};
</script>

Key Improvements Here:

  • ref instead of getElementById: Avoids conflicts if you have multiple charts on the page.
  • Chart Instance Storage: Lets us update the existing chart instead of creating a new one every time.
  • Prop Watching: Triggers chart updates whenever your API data arrives or changes.
  • Data Validation: Ensures we don't try to render a chart with empty data.
  • Cleanup: Destroys the chart when the component unmounts to prevent memory leaks.

Approach 2: Delay Rendering the Child Component (Simpler for One-Time Load)

If you don't need the chart to update dynamically, you can just wait until all data is loaded before rendering the BarChart component. Add a v-if in the parent component:

Modified Parent Component Template

<template>
  <div class="row">
    <div class="col-xl-12 col-md-6 col-sm-12">
      <!-- Only render BarChart when all data arrays have content -->
      <bar-chart 
        v-if="sent_applications.length && governorateName.length && active.length"
        :active-members="active" 
        :name="governorateName" 
        :total-sent-applications="sent_applications" 
      />
    </div>
  </div>
</template>

Parent Component Script Fix

Also, fix the unnecessary assignment to chartData (since $store.dispatch returns a Promise, not the data):

<script>
import BarChart from "@/components/UI/BarChart.vue";
export default {
  components: { BarChart }, // Removed unused AppCard reference
  data() {
    return {
      active: [],
      governorateName: [],
      sent_applications: [],
    };
  },
  created() {
    this.$store.dispatch("home/fetchAllData")
      .then((response) => {
        this.sent_applications = response.data.data.governerate.map(el => el.total_sent_applications);
        this.governorateName = response.data.data.governerate.map(el => el.name_ar);
        this.active = response.data.data.governerate.map(el => el.active_members);
      })
      .catch(error => {
        console.error('Failed to load chart data:', error);
      });
  },
};
</script>

This way, the BarChart only mounts once all the data is ready, so it initializes with valid data right away—no need for watchers in the child component.

Final Notes

  • Make sure your API returns arrays of the same length for total_sent_applications, name_ar, and active_members—mismatched lengths will cause chart rendering issues.
  • If you're using Vue 3, the watch syntax is slightly different, but the core logic remains the same.

内容的提问来源于stack exchange,提问作者Eslam Gohar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 19:12:51