recharts: CartesianGrid - Can no longer render CartesianGrid alone

Discussed in https://github.com/recharts/recharts/discussions/3906

<div type='discussions-op-text'>

Originally posted by sahillihas2021 October 26, 2023 I am displaying a multilinechart (Having multiple Y-Axis), it’s working perfectly fine in my local, but when I am deploying it to the internal dev server, it is giving an error for the same code on : ERROR

Cannot read properties of null (reading ‘ticks’) TypeError: Cannot read properties of null (reading ‘ticks’) at CartesianGrid.render (https://react.qa.he.az.devmfi.net/static/js/bundle.js:197529:66) at finishClassComponent (https://react.qa.he.az.devmfi.net/static/js/bundle.js:167192:35) at updateClassComponent (https://react.qa.he.az.devmfi.net/static/js/bundle.js:167149:28) at beginWork (https://react.qa.he.az.devmfi.net/static/js/bundle.js:168775:20) at HTMLUnknownElement.callCallback (https://react.qa.he.az.devmfi.net/static/js/bundle.js:153766:18) at Object.invokeGuardedCallbackDev (https://react.qa.he.az.devmfi.net/static/js/bundle.js:153810:20) at invokeGuardedCallback (https://react.qa.he.az.devmfi.net/static/js/bundle.js:153867:35) at beginWork$1 (https://react.qa.he.az.devmfi.net/static/js/bundle.js:173741:11) at performUnitOfWork (https://react.qa.he.az.devmfi.net/static/js/bundle.js:172988:16) at workLoopSync (https://react.qa.he.az.devmfi.net/static/js/bundle.js:172911:9)

Due to: [Refer the Code below)

yAxisComponents.push(
<YAxis
key={yAxisId}
yAxisId={yAxisId}
orientation={orientation}
axisLine={{ display: 'none' }}
tickLine={{ display: 'none' }}
tick={{
fill: tickColor,
fontSize: 16,
fontWeight: 'bold'
}}
/>
);

Code:

import React, { useState, useEffect } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';
import PropTypes from 'prop-types';
import Loading from '../../Loading/Loading';
function MultipleYAxesLineChart({ data, CustomXAxisTick, loading }) {
  MultipleYAxesLineChart.propTypes = {
    data: PropTypes.array.isRequired,
    CustomXAxisTick: PropTypes.array.isRequired,
    loading: PropTypes.bool.isRequired,
  };

  const [groupedData, setGroupedData] = useState({});
  useEffect(() => {
    const grouped = data.reduce((result, item) => {
      const { DeviceId } = item;
      if (!result[DeviceId]) {
        result[DeviceId] = [];
      }
      result[DeviceId].push(item);
      return result;
    }, {});
    setGroupedData(grouped);
  }, [data]);
  const deviceIds = Object.keys(groupedData);
  const numYAxes = deviceIds.length;
  const customColorScale = ['#72CCC0', '#5E89D8', '#FFD00A', '#A16CB4', '#6A864E', '#7E682D'];
  const [slicedDataByDeviceId, setSlicedDataByDeviceId] = useState({});
  useEffect(() => {
    const updatedSlicedData = {};
    const firstTimestamp = CustomXAxisTick[0];
    const lastTimestamp = CustomXAxisTick[CustomXAxisTick.length - 1];
    for (const DeviceId in groupedData) {
      const deviceData = groupedData[DeviceId];
      const extractedData = [];
      for (let i = firstTimestamp; i <= lastTimestamp; i += 60 * 1000) {
        const timestamp = i;
        const entry = deviceData.find((item) => item.sensorTimestamp === i);
        if (entry) {
          extractedData.push(entry);
        }
      }
      updatedSlicedData[DeviceId] = extractedData;
    }
    setSlicedDataByDeviceId(updatedSlicedData);
  }, [groupedData, CustomXAxisTick]);
  const yAxisComponents = [];
  for (let i = 0; i < numYAxes; i += 1) {
    const orientation = i < 3 ? 'left' : 'right';
    const yAxisId = `${orientation}${i + 1}`;
    const tickColor = customColorScale[i % customColorScale.length];
    yAxisComponents.push(
      <YAxis
        key={yAxisId}
        yAxisId={yAxisId}
        orientation={orientation}
        axisLine={{ display: 'none' }}
        tickLine={{ display: 'none' }}
        tick={{
          fill: tickColor,
          fontSize: 16,
          fontWeight: 'bold',
        }}
      />
    );
  }
  const [hiddenLines, setHiddenLines] = useState({});
  const CustomLegend = () => {
    const toggleLineVisibility = (DeviceId) => {
      setHiddenLines((prevHiddenLines) => ({
        ...prevHiddenLines,
        [DeviceId]: !prevHiddenLines[DeviceId],
      }));
    };
    const legendItems = [];
    for (const DeviceId in groupedData) {
      const deviceData = groupedData[DeviceId];
      if (deviceData.length > 0) {
        const { KPI_Label } = deviceData[0];
        const color = customColorScale[legendItems.length % customColorScale.length];
        legendItems.push(
          <div
            key={DeviceId}
            style={{
              display: 'flex',
              alignItems: 'center',
              marginRight: '20px',
              cursor: 'pointer',
              textDecoration: hiddenLines[DeviceId] ? 'line-through' : 'none',
            }}
            onClick={() => {
              toggleLineVisibility(DeviceId);
            }}
          >
            <div style={{ backgroundColor: color, width: '20px', height: '6px', marginRight: '5px' }} />
            <div>{KPI_Label}</div>
          </div>
        );
      }
    }
    return <div style={{ display: 'flex', alignItems: 'center' }}>{legendItems}</div>;
  };
  const containerStyle = {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    width: '1300px',
  };
  const [allLegendsClicked, setAllLegendsClicked] = useState(false);
  useEffect(() => {
    const areAllClicked = deviceIds.every((DeviceId) => hiddenLines[DeviceId]);
    setAllLegendsClicked(areAllClicked);
  }, [hiddenLines, deviceIds]);
  const CustomTooltip = ({ active, payload, label }) => {
    if (active && payload && payload.length) {
      const formatDate = (timestamp) => {
        const date = new Date(timestamp);
        const formattedDate = date.toLocaleDateString('en-US', {
          year: 'numeric',
          month: 'short',
          day: 'numeric',
          hour: 'numeric',
          minute: 'numeric',
        });
        return formattedDate;
      };
      return (
        <div style={{ background: 'white', border: '1px solid #ccc', padding: '10px', textAlign: 'left' }}>
          <div style={{ color: 'black', marginBottom: '5px' }}>Time: {formatDate(label)}</div>
          {payload.map((dataItem, index) => (
            <div key={`tooltip-item-${index}`} style={{ display: 'flex', alignItems: 'center', marginBottom: '5px' }}>
              <div style={{ width: '20px', height: '6px', background: dataItem.color, marginRight: '5px' }}></div>
              <div style={{ color: 'black' }}>
                {dataItem.payload.KPI_Label}: {dataItem.value || 0} {dataItem.payload.Unit_of_Measure}
              </div>
            </div>
          ))}
        </div>
      );
    }
    return null;
  };
  CustomTooltip.propTypes = {
    active: PropTypes.bool,
    payload: PropTypes.arrayOf(
      PropTypes.shape({
        color: PropTypes.string,
        value: PropTypes.number,
        name: PropTypes.string,
      })
    ),
    label: PropTypes.string,
  };
  return (
    <div style={containerStyle}>
      {loading || data.length === 0 ? (
        <Loading />
      ) : (
        <>
          <div>
            <LineChart width={1300} height={400} data={slicedDataByDeviceId} margin={{ right: 30, bottom: 40 }}>
              <CartesianGrid strokeDasharray="3 3" />
              {allLegendsClicked ? null : (
                <XAxis
                  dataKey="sensorTimestamp"
                  type="category"
                  allowDuplicatedCategory={false}
                  ticks={CustomXAxisTick}
                  interval={0}
                  tickFormatter={(timestamp, index) => {
                    const date = new Date(timestamp);
                    const hours = date.getHours();
                    const minutes = date.getMinutes();
                    const ampm = hours >= 12 ? 'PM' : 'AM';
                    const formattedHours = hours % 12 || 12;
                    const formattedMinutes = minutes.toString().padStart(2, '0');
                    if (index == 0) {
                      const curTimestamp = CustomXAxisTick[index + 1];
                      const timeDiff = curTimestamp - timestamp;
                      if (timeDiff >= 23 * 3600 * 1000 && timeDiff <= 25 * 3600 * 1000) {
                        const months = [
                          'Jan',
                          'Feb',
                          'Mar',
                          'Apr',
                          'May',
                          'Jun',
                          'Jul',
                          'Aug',
                          'Sep',
                          'Oct',
                          'Nov',
                          'Dec',
                        ];
                        const month = months[date.getMonth()];
                        const day = date.getDate();
                        return `${month}-${day}`;
                      }
                    } else if (index > 0) {
                      const prevTimestamp = CustomXAxisTick[index - 1];
                      const timeDiff = timestamp - prevTimestamp;
                      if (timeDiff >= 23 * 3600 * 1000 && timeDiff <= 25 * 3600 * 1000) {
                        const months = [
                          'Jan',
                          'Feb',
                          'Mar',
                          'Apr',
                          'May',
                          'Jun',
                          'Jul',
                          'Aug',
                          'Sep',
                          'Oct',
                          'Nov',
                          'Dec',
                        ];
                        const month = months[date.getMonth()];
                        const day = date.getDate();
                        return `${month}-${day}`;
                      }
                    }
                    return `${formattedHours}:${formattedMinutes} ${ampm}`;
                  }}
                  tick={{ fontSize: 14 }}
                  tickMargin={25}
                />
              )}
              {yAxisComponents}
              <Tooltip content={<CustomTooltip />} />
              {deviceIds.slice(0, numYAxes).map((DeviceId, index) => (
                <Line
                  key={DeviceId}
                  type="monotone"
                  dataKey="value"
                  name={DeviceId}
                  data={slicedDataByDeviceId[DeviceId]}
                  yAxisId={yAxisComponents[index].props.yAxisId}
                  stroke={customColorScale[index % customColorScale.length]}
                  hide={hiddenLines[DeviceId]}
                  dot={false}
                />
              ))}
            </LineChart>
          </div>
          <CustomLegend />
        </>
      )}
    </div>
  );
}
export default MultipleYAxesLineChart;

image

The above code is accepting the data (To be displayed on the UI Line chart), CustomXAxisTick (X-axis values), loading (To load the page/refresh).

How can I fix this as I am not able to replicate the same scenario in my local server (Laptop).

Thankyou !</div>

About this issue

  • Original URL
  • State: closed
  • Created 8 months ago
  • Comments: 15 (15 by maintainers)

Commits related to this issue

Most upvoted comments

Released fix in 2.9.1, resolving