javascriptreactjscharts

React Chart Not Updating When apiData Remains the Same


I'm working on a React component that displays a line chart using react-chartjs-2. The chart should update whenever new data (apiData) is received from an API. The issue I'm facing is that if the new apiData value is the same as the previous one, the chart does not update or re-render.

Here’s the relevant part of my component:

import React, { useState, useContext, useEffect } from 'react';
import { Line } from 'react-chartjs-2';
import { DataContext } from '../../context/DataContext';
import 'chart.js/auto';
import styled from 'styled-components';

const MAX_DATA_POINTS = 100;

const Container = styled.div`
    width: 99%;
    height: 100%;
`;

const Graph = ({ apiData, graphLabel }) => {
    const { streaming, clearFlag, setClearFlag } = useContext(DataContext);

    const [graphData, setGraphData] = useState(() => {
        const storedData = sessionStorage.getItem('graphData');
        return storedData ? JSON.parse(storedData) : [];
    });

    const [key, setKey] = useState(0); // new state to track re-renders

    useEffect(() => {
        if (streaming) {
            setGraphData(prevData => {
                const newData = [
                    ...prevData,
                    { time: new Date().toLocaleTimeString(), data: apiData }
                ];
                // Limit the number of data points to preserve interval
                if(newData.length > MAX_DATA_POINTS){
                    newData.shift();
                }

                sessionStorage.setItem('graphData', JSON.stringify(newData));
                setKey(prevKey => prevKey + 1); // trigger re-render
                return [...newData]; // return new array to trigger re-render 
            });
        }
    }, [apiData, streaming]);

    const dataPoints = {
        labels: graphData.map(d => d.time),
        datasets: [
            {
                label: graphLabel,
                data: graphData.map(d => d.data),
                fill: false,
                backgroundColor: '#F3F4F6',
                borderColor: '#0077C8',
            },
        ],
    };

    useEffect(() => {
        if (clearFlag) {
            setGraphData([]);
            sessionStorage.removeItem('graphData');
            setClearFlag(false);
        }
    }, [clearFlag])

    const options = {
        scales: {
            y: {
                beginAtZero: true,
                max: 1000,
            },
        },
        maintainAspectRatio: false,
        animation: {
            duration: 0,
        },
        plugins: {
            legend: {
                labels: {
                    font: {
                        size: 18,
                        weight: 'bold',
                    },
                    color: 'black'
                }
            }
        }
    };

    return (
        <Container>
            <Line data={dataPoints} options={options} />
        </Container>
    );
};

export default Graph;

Solution

  • I just add a timestamp to compare and trigger a re-render.

    useEffect(() => {
            if (streaming && apiData !== null) {
                setGraphData(prevData => {
                    const newData = [
                        ...prevData,
                        { time: new Date().toLocaleTimeString(), data: apiData }
                    ];
                    // Limit the number of data points to preserve interval
                    if(newData.length > MAX_DATA_POINTS){
                        newData.shift();
                    }
    
                    sessionStorage.setItem('graphData', JSON.stringify(newData));
                    return [...newData]; // return new array to trigger re-render
                });
            }
        }, [apiData, timestamp, streaming]);
    

    Thanks stackoverflow for nothing again.