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

React useEffect初始化Chart.js报错:Cannot read properties of null (reading 'getContext')

React中useRef+Chart.js异步数据初始化的时序问题解决

问题描述

在React组件中使用useRef定位canvas元素,初始化Chart.js图表实例:

  • 用硬编码静态数据初始化时完全正常;
  • 组件挂载后异步从API获取数据,待数据返回后初始化图表,抛出致命错误导致应用崩溃。

错误控制台输出:

Uncaught TypeError: Cannot read properties of null (reading 'getContext')
    at AnalyticsDashboard.js:31:35
    at commitHookEffectListMount (react-dom.development.js:23150:26)
    at commitPassiveMountOnFiber (react-dom.development.js:24408:13)

已尝试方案:

  • 在调用getContext前添加if (!canvasRef.current) return;:错误消失,但图表永远不会渲染;
  • 移除if (loading) return...提前返回逻辑:图表正常渲染,但页面会先显示空的canvas,体验异常。

错误原因分析

核心问题是状态更新的时序不匹配:

  1. API请求完成后,会先执行setAnalyticsData(data)更新数据状态,此时loading仍为true;
  2. 组件重新渲染时,因为loading为true,会直接返回加载提示的<div>,canvas元素并未被渲染到DOM中,canvasRef.current保持为null;
  3. 监听analyticsData的useEffect会因为数据状态更新触发执行,此时尝试调用canvasRef.current.getContext()自然会抛出null读取属性的错误。

而第一种尝试方案中,虽然避免了错误,但当后续loading变为false、canvas渲染完成时,analyticsData没有再次变化,监听它的useEffect不会重新执行,所以图表永远不会初始化。

修复方案

方案1:同步监听数据与加载状态

调整初始化图表的useEffect依赖,同时监听analyticsData和loading,确保数据已获取且canvas已渲染后再初始化图表:

import { useEffect, useRef, useState } from 'react';
import Chart from 'chart.js/auto';

export default function AnalyticsDashboard() {
  const canvasRef = useRef(null);
  const [analyticsData, setAnalyticsData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchMetrics() {
      try {
        const response = await fetch('/api/v1/analytics');
        const data = await response.json();
        setAnalyticsData(data);
      } catch (err) {
        console.error("Failed to fetch", err);
      } finally {
        setLoading(false);
      }
    }
    fetchMetrics();
  }, []);

  useEffect(() => {
    // 必须同时满足:数据存在、加载完成、canvas已挂载
    if (!analyticsData || loading || !canvasRef.current) return;

    const ctx = canvasRef.current.getContext('2d'); 
    const chartInstance = new Chart(ctx, {
      type: 'bar',
      data: {
        labels: analyticsData.labels,
        datasets: [{ data: analyticsData.values }]
      }
    });

    return () => chartInstance.destroy();
  }, [analyticsData, loading]); // 添加loading作为依赖

  if (loading) return <div>Loading metrics...</div>;

  return (
    <div className="chart-container">
      <h2>Monthly Traffic</h2>
      <canvas ref={canvasRef} id="trafficChart" width="400" height="200"></canvas>
    </div>
  );
}

方案2:调整渲染逻辑,始终保留canvas(可选)

如果希望加载状态下也保留canvas元素(通过CSS隐藏),可以避免ref为空的情况:

import { useEffect, useRef, useState } from 'react';
import Chart from 'chart.js/auto';

export default function AnalyticsDashboard() {
  const canvasRef = useRef(null);
  const [analyticsData, setAnalyticsData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchMetrics() {
      try {
        const response = await fetch('/api/v1/analytics');
        const data = await response.json();
        setAnalyticsData(data);
      } catch (err) {
        console.error("Failed to fetch", err);
      } finally {
        setLoading(false);
      }
    }
    fetchMetrics();
  }, []);

  useEffect(() => {
    if (!analyticsData || !canvasRef.current) return;

    const ctx = canvasRef.current.getContext('2d'); 
    const chartInstance = new Chart(ctx, {
      type: 'bar',
      data: {
        labels: analyticsData.labels,
        datasets: [{ data: analyticsData.values }]
      }
    });

    return () => chartInstance.destroy();
  }, [analyticsData]);

  return (
    <div className="chart-container">
      <h2>Monthly Traffic</h2>
      {/* 用loading状态控制canvas的显示 */}
      <canvas 
        ref={canvasRef} 
        id="trafficChart" 
        width="400" 
        height="200"
        style={{ display: loading ? 'none' : 'block' }}
      ></canvas>
      {loading && <div>Loading metrics...</div>}
    </div>
  );
}

关键说明

  • React的ref只有在对应元素被渲染到DOM后才会赋值,所以必须确保元素已挂载再读取ref;
  • useEffect的依赖数组必须包含所有影响逻辑执行的状态,否则会出现时序问题;
  • 销毁Chart实例的清理函数很重要,避免组件卸载或状态更新时出现内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 04:17:26