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

Next.js中ApexCharts状态更新后所有图表显示相同数据问题

问题:Next.js中ApexCharts面积图状态更新后全部显示最后一组数据

我在Next.js项目里用React+ApexCharts实现了多个面积图sparklines。当statsData状态更新后,所有图表本该加载对应新数据,但最终全部显示成了最后一个图表的数据。已确认状态更新正常,每个图表也配置了唯一key,怀疑问题出在图表options的更新方式上——根据ApexCharts文档,应该更新options对象的最外层属性。

以下是组件简化版本:

import dynamic from 'next/dynamic';
import { useEffect, useState } from 'react';
const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });

const Apdex = ({ data, startTime, endTime, theme, selectedTimeRange }) => {
  const [statsData, setStatsData] = useState([]);

  useEffect(() => {
    if (data.length === 0) return;

    const startTimeEpoch = parseInt(startTime);
    const endTimeEpoch = parseInt(endTime);
    const {
      binTotalRequests,
      binAvgResponseTimes,
      binFailureRates,
      binFailedRequests,
      overallTotalRequests,
      overallAvgResponseTime,
      overallFailureRate,
      overallFailedRequests,
    } = computeMetrics(data, selectedTimeRange, startTimeEpoch, endTimeEpoch);

    const updatedStats = [
      { title: 'Total Requests', data: binTotalRequests, overall: overallTotalRequests },
      { title: 'Avg Response Time', data: binAvgResponseTimes, overall: overallAvgResponseTime },
      { title: 'Failure Rate', data: binFailureRates, overall: overallFailureRate },
      { title: 'Failed Requests', data: binFailedRequests, overall: overallFailedRequests },
    ];

    setStatsData(updatedStats);
  }, [data, startTime, endTime]);

  const createChartOptions = (id, title, data, overall) => ({
    chart: {
      id,
      group: 'sparks',
      type: 'area',
      grid: { padding: { left: 0, right: 0 } },
      sparkline: { enabled: true },
      dropShadow: { enabled: true, top: 1, left: 1, blur: 2, opacity: 0.2 },
    },
    series: [{ data }],
    stroke: { curve: 'smooth', width: 0.5 },
    markers: { size: 0 },
    colors: ['#fff'],
    tooltip: { x: { show: false }, y: { title: { formatter: () => '' } } },
    title: { text: overall, offsetX: 20, style: { fontSize: '15px', color: '#fff' } },
    subtitle: { text: title, offsetX: 20, offsetY: 20, style: { fontSize: '10px', color: '#fff' } },
  });

  return (
    <div className="w-full flex flex-col h-full">
      <div className="flex-grow" style={{ flexBasis: '60%' }}>
        <div className="grid h-full grid-cols-1 sm:grid-cols-2">
          {statsData.length === 0
            ? [...Array(4)].map((_, index) => (
                <div key={index} className={`${theme} bg-white rounded-lg m-1 pt-1 shadow flex flex-col justify-center items-center animate-pulse`}>
                  <div className="text-lg text-gray-500">Loading...</div>
                </div>
              ))
            : statsData.map((stat, index) => (
                <div key={`chart-container-${index}`} className={`${theme} bg-white rounded-lg m-1 pt-1 shadow flex flex-col justify-center items-center`}>
                  <Chart
                    options={createChartOptions(`chart-${index}`, stat.title, stat.data, stat.overall)}
                    series={[{ data: stat.data }]}
                    type="area"
                    height="55%"
                    width="100%"
                    key={`chart-${index}`}
                  />
                </div>
              ))}
        </div>
      </div>
    </div>
  );
};

export default Apdex;

状态更新前的图表

首次渲染完成后的图表

状态更新后的图表

状态更新后的图表


解决方案

核心问题

问题源于ApexCharts的实例复用机制与React引用类型更新的冲突:

  1. 每次渲染时动态生成的options对象,其内部嵌套的series属性无法被ApexCharts正确识别为更新
  2. 虽给Chart组件设置了唯一key,但如果options/series的引用更新不彻底,图表实例会复用旧数据

修复步骤

  1. 用useMemo缓存图表配置:避免在render阶段重复生成options,确保只有依赖变化时才更新配置
  2. 确保数据引用唯一:每次更新statsData时,给每个数据数组创建新引用,避免ApexCharts认为数据未变化
  3. 移除冗余属性:options中已定义series,无需再单独传给Chart组件,避免数据冲突

修改后的代码

import dynamic from 'next/dynamic';
import { useEffect, useState, useMemo } from 'react';
const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });

const Apdex = ({ data, startTime, endTime, theme, selectedTimeRange }) => {
  const [statsData, setStatsData] = useState([]);

  useEffect(() => {
    if (data.length === 0) return;

    const startTimeEpoch = parseInt(startTime);
    const endTimeEpoch = parseInt(endTime);
    const {
      binTotalRequests,
      binAvgResponseTimes,
      binFailureRates,
      binFailedRequests,
      overallTotalRequests,
      overallAvgResponseTime,
      overallFailureRate,
      overallFailedRequests,
    } = computeMetrics(data, selectedTimeRange, startTimeEpoch, endTimeEpoch);

    // 给每个数据数组创建新引用,确保React能识别变化
    const updatedStats = [
      { title: 'Total Requests', data: [...binTotalRequests], overall: overallTotalRequests },
      { title: 'Avg Response Time', data: [...binAvgResponseTimes], overall: overallAvgResponseTime },
      { title: 'Failure Rate', data: [...binFailureRates], overall: overallFailureRate },
      { title: 'Failed Requests', data: [...binFailedRequests], overall: overallFailedRequests },
    ];

    setStatsData(updatedStats);
  }, [data, startTime, endTime, selectedTimeRange]); // 补全依赖数组

  // 用useMemo缓存所有图表配置,仅当statsData变化时重新生成
  const chartConfigs = useMemo(() => {
    return statsData.map((stat, index) => ({
      options: {
        chart: {
          id: `chart-${index}`,
          group: 'sparks',
          type: 'area',
          grid: { padding: { left: 0, right: 0 } },
          sparkline: { enabled: true },
          dropShadow: { enabled: true, top: 1, left: 1, blur: 2, opacity: 0.2 },
        },
        series: [{ data: stat.data }],
        stroke: { curve: 'smooth', width: 0.5 },
        markers: { size: 0 },
        colors: ['#fff'],
        tooltip: { x: { show: false }, y: { title: { formatter: () => '' } } },
        title: { text: stat.overall, offsetX: 20, style: { fontSize: '15px', color: '#fff' } },
        subtitle: { text: stat.title, offsetX: 20, offsetY: 20, style: { fontSize: '10px', color: '#fff' } },
      },
      key: `chart-${index}`
    }));
  }, [statsData]);

  return (
    <div className="w-full flex flex-col h-full">
      <div className="flex-grow" style={{ flexBasis: '60%' }}>
        <div className="grid h-full grid-cols-1 sm:grid-cols-2">
          {statsData.length === 0
            ? [...Array(4)].map((_, index) => (
                <div key={index} className={`${theme} bg-white rounded-lg m-1 pt-1 shadow flex flex-col justify-center items-center animate-pulse`}>
                  <div className="text-lg text-gray-500">Loading...</div>
                </div>
              ))
            : chartConfigs.map((config, index) => (
                <div key={`chart-container-${index}`} className={`${theme} bg-white rounded-lg m-1 pt-1 shadow flex flex-col justify-center items-center`}>
                  <Chart
                    options={config.options}
                    type="area"
                    height="55%"
                    width="100%"
                    key={config.key}
                  />
                </div>
              ))}
        </div>
      </div>
    </div>
  );
};

export default Apdex;

额外注意事项

  • 检查computeMetrics函数,确保返回的bin数据是新数组而非原数据的引用
  • 若问题仍存在,可尝试给chart.id添加状态哈希值,强制图表实例重新创建
  • 始终保证useEffect依赖数组完整,避免遗漏导致状态不更新

内容的提问来源于stack exchange,提问作者Ajit Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 16:15:03