ApexCharts chart.render()报错排查:元素/Window未找到及可选链异常
解决Next.js中ApexCharts的三个核心问题
问题1:Element not found error
原因:你在组件函数顶层直接执行chart.render(),此时React还未完成DOM挂载,document.querySelector('#chart')找不到对应的元素。
修复:把图表初始化和渲染逻辑放到useEffect钩子中,确保DOM元素已挂载后再执行。同时要监听数据变化,当图表依赖的SaleCount和Dates更新时重新渲染,还要在组件卸载前销毁旧图表,避免内存泄漏。
问题2:可选链与刷新后报错反复
原因:初始状态下i是undefined,直接访问i.collections会触发错误;添加可选链后,数据未加载时si为undefined,si?.map不会执行,避免了报错。但刷新页面时数据需要重新请求,这段时间i仍为undefined,去掉可选链就会再次触发错误。
修复:
- 给
i设置初始默认值,比如useState({ collections: [] }),避免初始状态为undefined - 始终用可选链或空值合并运算符安全访问
i.collections - 增加加载状态,数据未返回时显示加载提示,避免空数据导致图表异常
问题3:window not found error
原因:Next.js默认是服务端渲染(SSR),ApexCharts依赖浏览器的window对象,服务端渲染阶段不存在window,因此会报错。
修复:
- 动态导入ApexCharts,仅在客户端环境加载
- 用
typeof window !== 'undefined'做判断,确保图表逻辑只在客户端执行 - 或使用
next/dynamic导入图表组件时禁用SSR
完整修正代码
import React, { useEffect, useState, useMemo } from "react"; import Head from 'next/head' import styles from '../../../../styles/Home.module.css' import CurrencyFormat from 'react-currency-format'; import Image from "next/image"; // 动态导入ApexCharts,规避SSR时的window问题 const loadApexCharts = async () => { if (typeof window !== 'undefined') { const { default: ApexCharts } = await import('apexcharts'); return ApexCharts; } return null; }; function App() { // 给i设置初始默认值,避免undefined const [i, setSeti] = useState({ collections: [] }); const [countries, setCountries] = useState(); const [loading, setLoading] = useState(true); let chartInstance = null; // 保存图表实例,用于销毁 useEffect(() => { fetch( "https://api.reservoir.tools/collections/daily-volumes/v1?id=0xed5af388653567af2f388e6224dc7c4b3241c544&limit=10" ) .then((response) => response.json()) .then((response) => { setSeti(response); setLoading(false); }) .catch(err => { console.error('数据请求失败:', err); setLoading(false); }); }, []); // 用useMemo缓存数据处理结果,避免重复计算 const { Dates, SaleCount } = useMemo(() => { const SaleCountt = []; const Datess = []; const si = i?.collections || []; si.forEach(item => { SaleCountt.push(Math.round(item.sales_count)); const date = new Date(item.timestamp * 1000).toLocaleDateString("en-US"); Datess.push(date); }); return { Dates: Datess.reverse(), SaleCount: SaleCountt.reverse() }; }, [i]); useEffect(() => { // 仅在客户端且数据加载完成后初始化图表 if (!loading && typeof window !== 'undefined') { loadApexCharts().then(ApexCharts => { if (!ApexCharts) return; const options = { chart: { type: 'bar', height: 350 }, colors: ["#2E268D"], title: { text: 'Sale Count', align: 'left' }, series: [ { name: 'sales', data: SaleCount, } ], xaxis: { type: 'datetime', categories: Dates } }; // 销毁旧图表,避免重复渲染 if (chartInstance) { chartInstance.destroy(); } const chartElement = document.querySelector('#chart'); if (chartElement) { chartInstance = new ApexCharts(chartElement, options); chartInstance.render(); } }); } // 组件卸载时销毁图表 return () => { if (chartInstance) { chartInstance.destroy(); } }; }, [loading, Dates, SaleCount]); if (loading) { return <div className={styles.Apps}>加载中...</div>; } return ( <div className={styles.Apps}> <div className={styles.StatusTitles}>Apbo detailed analytics</div> <div className={styles.StatusBox}> <div className={styles.StatusBoxItemm}> <div id="chart" className={styles.chartt}></div> </div> </div> </div> ); } export default App;
内容的提问来源于stack exchange,提问作者Sahankaan
相关产品推荐
相关产品推荐

