React集成Lightweight Charts时出现'Uncaught TypeError: Cannot read property 'getContext' of null'错误的求助
Let's break down why you're hitting this error and walk through how to fix it. The core problem here is that you aren't cleaning up WebSocket connections and interval timers properly when your component re-renders or unmounts. This leaves stale callbacks that try to interact with chart instances tied to DOM elements that no longer exist—hence the getContext null error.
Why This Happens
Your useEffect hook runs every time fourHourData changes (since it’s in the dependency array). Every time it executes:
- You create a brand new WebSocket connection
- You spin up a new interval timer
- You create a fresh chart instance, then remove the old one in the cleanup function
But the old WebSocket and timer don’t get destroyed—they keep running in the background. When their callbacks fire, they try to use the old candleStickSeries or chart instance that’s already been detached from the DOM. That’s when the error pops up.
Step-by-Step Fixes
1. Clean Up WebSocket Connections
Add code to close the WebSocket in the useEffect cleanup function so old connections don’t linger and fire stale callbacks.
2. Clear Interval Timers
Store the interval ID in a ref, then clear it in the cleanup function to stop the timer from running after the component re-renders or unmounts.
3. Guard Against Null DOM References
Always check if chartRef.current exists before creating the chart—this prevents attempts to initialize a chart on an unmounted element.
4. Fix Prop Destructuring (Small but Critical)
Your child component was receiving props as fourHourData, but you should destructure chartData explicitly to avoid confusion and ensure correct dependency tracking.
Modified Child Component Code
Here’s your FirstChart component with all fixes applied:
function FirstChart({ chartData, setHistoricalData }) { const [signals, setSignals] = useState([]); const [loading, setLoading] = useState(true); const chartRef = React.useRef(); const intervalRef = React.useRef(null); // Store interval ID for cleanup const clientRef = React.useRef(null); // Store WebSocket instance for cleanup useEffect(() => { // Bail if DOM element doesn't exist yet if (!chartRef.current) return; console.log("DOM re-rendered."); // Initialize WebSocket and save to ref const client = new W3CWebSocket("wss://stream.binance.com:9443/ws/btcusdt@kline_4h"); clientRef.current = client; const chart = createChart(chartRef.current, { watermark: { color: "white", visible: true, fontSize: 18, horzAlign: "left", vertAlign: "top", }, timeScale: { timeVisible: true, borderColor: "whitesmoke", }, layout: { backgroundColor: "#131722", textColor: "whitesmoke", fontSize: 12, fontFamily: "Arial", }, priceScale: { borderColor: "whitesmoke", }, grid: { vertLines: { style: 0, color: "rgba(70, 130, 180, 0.5)", visible: true, }, horzLines: { style: 0, color: "rgba(70, 130, 180, 0.5)", visible: true, }, }, }); const candleStickSeries = chart.addCandlestickSeries({ upColor: "#0CCE6B", downColor: "#ED254E", borderVisible: false, wickVisible: true, borderColor: "white", wickColor: "white", borderUpColor: "#84f766", borderDownColor: "#ff6939", wickUpColor: "#84f766", wickDownColor: "#ff6939", }); // Only set data if we have valid chartData if (chartData.length > 0) { candleStickSeries.setData(chartData); } client.onmessage = (event) => { let message = JSON.parse(event.data); // Guard against stale series instance if (candleStickSeries) { candleStickSeries.update({ time: message.k.t / 1000, open: parseFloat(message.k.o), // Parse Binance's string values to numbers high: parseFloat(message.k.h), low: parseFloat(message.k.l), close: parseFloat(message.k.c), }); } }; const getSignalData = () => { const url = "YOUR_SIGNAL_API_URL"; // Replace with your actual API endpoint fetch(url, { method: "GET", mode: "cors", }) .then(checkStatus) .then(json) .then((response) => { const signalData = response.map((data) => { return { // Your marker data transformation logic here }; }); // Guard against stale series before updating markers if (candleStickSeries) { candleStickSeries.setMarkers(signalData); } setSignals([...signalData.reverse()]); setLoading(false); }) .catch((error) => { console.log(error); }); }; getSignalData(); // Save interval ID to ref for cleanup intervalRef.current = setInterval(() => { getSignalData(); console.log("Fetched Long Active Chart again..."); }, 60 * 1000); // Cleanup function: runs on re-render and unmount return () => { // Close WebSocket connection if (clientRef.current) { clientRef.current.close(); } // Clear interval timer if (intervalRef.current) { clearInterval(intervalRef.current); } // Destroy chart instance chart.remove(); }; }, [chartData]); return ( <Row className="my-2"> <Col lg={8} className="d-md-10 d-xl-block mx-auto mb-5 py-5 lightweight-chart" ref={chartRef} /> </Row> ); } export default FirstChart;
Key Changes Explained
- Refs for Cleanup: Used refs to track the WebSocket instance and interval ID, so we can access and clean them up in the useEffect cleanup function.
- Guard Clauses: Added checks to ensure we never call methods on stale or null chart/series instances.
- Number Parsing: Fixed the WebSocket update to parse Binance's string price values to numbers (Lightweight Charts expects numeric inputs).
- Prop Destructuring: Corrected the component props to properly destructure
chartDatafor clarity and correct dependency tracking.
Additional Optimization Tip
In your parent component, consider memoizing the chart data arrays with useMemo to avoid unnecessary re-renders of the chart components. For example:
const fourHourDataMemo = useMemo(() => fourHourData, [fourHourData]);
Then pass fourHourDataMemo to FirstChart instead of the raw state—this prevents re-renders if the array reference changes but the data itself doesn’t.
This should eliminate the getContext null error while keeping all your real-time and signal functionality intact.
内容的提问来源于stack exchange,提问作者Glemin5011

