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
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. Resolve API response data
Based on your Node.JS API returned data, it would be as example below:
The first element will not have the
_idproperty.I believe these two lines will throw the error as
_iddoesn't exist.You need either:
dataarray.totalUsersandtotalNewUsersproperties.2. React Calendar
onChangeeventMeanwhile, I noticed that the
onChangeevent does not trigger thehandleCalendarChangein the year view. Instead, you should use theonClickMonthevent.3. Use React hook
useEffectto update thefilteredDatawhenselectedYearchangedThe
filteredDatawill only initialize but not update when the component is initialized. Change thefilteredDatato store the state variable. ApplyuseEffectwithselectedYearas dependencies, thus when it is changed, you need to update thefilteredDataaccordingly.Demo @ StackBlitz