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引用类型更新的冲突:
- 每次渲染时动态生成的options对象,其内部嵌套的
series属性无法被ApexCharts正确识别为更新 - 虽给Chart组件设置了唯一key,但如果options/series的引用更新不彻底,图表实例会复用旧数据
修复步骤
- 用useMemo缓存图表配置:避免在render阶段重复生成options,确保只有依赖变化时才更新配置
- 确保数据引用唯一:每次更新statsData时,给每个数据数组创建新引用,避免ApexCharts认为数据未变化
- 移除冗余属性: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
相关产品推荐
相关产品推荐

