I'm encountering a TypeError while working on a React project where I'm using a Chart component to display user analytics data fetched from an API. Additionally, I'm seeking guidance on how to implement filtering by year. Here's the error message I'm seeing:

TypeError: Cannot read properties of undefined (reading 'year')

Code Snippets:

Chart Component:

import "./Chart.scss";
import { LineChart, Line, XAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import React, { useState } from 'react'; // Don't forget to import React if you're using JSX
import Calendar from 'react-calendar'; // Import the Calendar component

const Chart = ({ title, data, datakeys, grid }) => {
  const [selectedYear, setSelectedYear] = useState(new Date().getFullYear()); // Initialize selectedYear state with the current year

  // Extracting month values from the data array
  const months = data.map(item => item._id.month);

  // Filter data based on the selected year
  const filteredData = data.filter(item => item._id.year === selectedYear);

  // Handle calendar change
  const handleCalendarChange = date => {
    setSelectedYear(date.getFullYear());
  };

  return (
    <div className="chart">
      <div className="topOfchart">
        <div className="left">
          <h3 className="chartTitle">
              {title}
          </h3>
        </div>
        <div className="right">
          <Calendar
            onChange={handleCalendarChange}
            value={new Date(selectedYear, 0, 1)} // Set the initial value to January 1st of the selected year
            view="year" // Display the calendar in year view
          />
        </div>
      </div>
      <ResponsiveContainer width="100%" aspect={4/1}>
        <LineChart data={filteredData}>
          <XAxis dataKey="_id.month" stroke="#5550bd" tick={{fontSize: 12}} /> {/* Accessing the month value from the _id object */}
          <Line type="monotone" dataKey={datakeys} stroke="#5550bd"/>
          <Tooltip/>
          { grid && <CartesianGrid stroke="#e0dfdf" strokeDasharray="5 5"/> }
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
};

export default Chart;

and Home Page Component:

import  Chart  from "../../components/chart/Chart"
import FeaturedInfo from "../../components/featuredinfo/FeaturedInfo"
import { WidgetLg } from "../../components/widgetLg/WidgetLg"
import { WidgetSm } from "../../components/widgetSm/WidgetSm"
import "./Home.scss"
import  {Data}  from "../../data";
import { useEffect, useState } from "react";
import api from "../../api";

const Home = () => {
  const MONTHS=[
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Agu", "Sep", "Oct", "Nov", "Dec"
  ];

  const [userStats, setUserStats] = useState([]);

  useEffect(()=>{
    const getStats = async ()=>{
      try {
        const res = await api.get("/users/stats",{
          headers:{
            token:
            "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY1YjkxMWYwMjM5MjAxMTBhNTA3NGJmNCIsImlzQWRtaW4iOnRydWUsImlhdCI6MTcxMTcwMzAzMiwiZXhwIjoxNzE0NzI3MDMyfQ.ClT9x0c6v_Bfm9zvhmDAwUPDHWkm-Ws-yZ1CfNhX-5Y"
          }
        });
        setUserStats(res.data)
      } catch (error) {
        console.log(error);
      }
      
    };
    getStats();
  },[])

  console.log(userStats);
  return (
    <div className="home">
      <FeaturedInfo/>
      <Chart data={userStats} title="User Analytics" grid datakeys="newUsers"/>
      <div className="homeWidgets">
        <WidgetSm/>
        <WidgetLg/>
      </div>
    </div>
  )
}

export default Home

i got this output this picher give this code out put

and i expected this with filter by year

here is backend code :

router.get("/stats", async (req, res) => {
    const today = new Date();
    const lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, today.getDate()); // Get the date of last month

    const monthsArray = [
        "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"
    ];

    try {
        // Calculate total users of all time
        const totalUsersData = await User.aggregate([
            {
                $group: {
                    _id: null,
                    totalUsers: { $sum: 1 }
                }
            }
        ]);

        // Calculate total new users of last month
        const totalNewUsersData = await User.aggregate([
            {
                $match: { createdAt: { $gte: lastMonth } } // Filter users created within the last month
            },
            {
                $group: {
                    _id: null,
                    totalNewUsers: { $sum: 1 }
                }
            }
        ]);

        let totalUsers;
        if (totalUsersData.length > 0) {
            totalUsers = totalUsersData[0].totalUsers;
        } else {
            totalUsers = 0;
        }

        let totalNewUsers;
        if (totalNewUsersData.length > 0) {
            totalNewUsers = totalNewUsersData[0].totalNewUsers;
        } else {
            totalNewUsers = 0;
        }

        // Retrieve monthly user data
        const monthlyUserData = await User.aggregate([
            {
                $group: {
                    _id: {
                        year: { $year: "$createdAt" },
                        month: { $arrayElemAt: [monthsArray, { $subtract: [{ $month: "$createdAt" }, 1] }] }
                    },
                    total: { $sum: 1 }, // Total users
                    newUsers: {
                        $sum: {
                            $cond: [
                                { $gte: ["$createdAt", lastMonth] }, // Check if user was created within the last month
                                1,
                                0
                            ]
                        }
                    }
                }
            },
            {
                $project: {
                    _id: 1,
                    total: 1,
                    newUsers: 1
                }
            }
        ]);

        const data = [
            {
                totalUsers: totalUsers,
                totalNewUsers: totalNewUsers
            },
            ...monthlyUserData
        ];

        res.status(200).json(data);
    } catch (error) {
        res.status(500).json(error);
    }
});
1

There are 1 best solutions below

0
Yong Shun On BEST ANSWER

1. Resolve API response data

Based on your Node.JS API returned data, it would be as example below:

[
  {
    totalUsers: 1150,
    totalNewUsers: 160,
  },
  {
    _id: {
      month: 12,
      year: 2023,
    },
    total: 990,
    newUsers: 15,
  },
  ...
]

The first element will not have the _id property.

I believe these two lines will throw the error as _id doesn't exist.

const months = data.map(item => item._id.month);

// Filter data based on the selected year
const filteredData = data.filter(item => item._id.year === selectedYear);

You need either:

  1. Exclude the first element of the data array.
const months = data.slice(1).map((item) => item._id.month);
  1. Node.JS API returns a response that does not include the first object with the totalUsers and totalNewUsers properties.

2. React Calendar onChange event

Meanwhile, I noticed that the onChange event does not trigger the handleCalendarChange in the year view. Instead, you should use the onClickMonth event.

<Calendar
    onClickMonth={handleCalendarChange}
    value={new Date(selectedYear, 0, 1)} // Set the initial value to January 1st of the selected year
    view="year" // Display the calendar in year view
/>

3. Use React hook useEffect to update the filteredData when selectedYear changed

The filteredData will only initialize but not update when the component is initialized. Change the filteredData to store the state variable. Apply useEffect with selectedYear as dependencies, thus when it is changed, you need to update the filteredData accordingly.

const [filteredData, setFilteredData] = useState([])

useEffect(() => {
    const _filteredData = data
    .slice(1)
    .filter((item) => item._id.year === selectedYear)

    setFilteredData(_filteredData);
  }, [selectedYear]);

Demo @ StackBlitz