There are issues when it comes to dependency array giving inconsistent data when using value stringify in dependency array. When I filtered the data by cities the charts fails to filter out data when y variable is the same. Say for example I was filtering meetings in Tokyo, JP with 2 meetings and it will not change dot plot horizontally to current city to say Toronto, CN which also has 2 meetings. This is consistent inconsistency in the data whenever you have the city with the same number of objects, fails to notice the change in content of those objects fyi city name reference.
APP Main Component Code
import React from 'react';
import { useEffect, useState } from 'react';
import CitySearch from './components/CitySearch';
import EventList from './components/EventList';
import NumberOfEvents from './components/NumberOfEvents'
import { getEvents, extractLocations } from './api';
import './App.css';
import { CityAlert, NumberAlert, EventAlert } from './components/Alert';
import CityEventsChart from './components/CityEventsChart';
import EventGenresChart from './components/EventGenresChart';
const App = () => {
const [events, setEvents] = useState([]);
const [allLocations, setAllLocations] = useState([]);
const [currentCity, setCurrentCity] = useState("See all cities");
const [currentNOE, setCurrentNOE] = useState(32);
const [cityAlert, setCityAlert] = useState("");
const [numberAlert, setNumberAlert] = useState("");
const [eventAlert, setEventAlert] = useState("");
const [isOnline, setIsOnline] = useState(navigator.onLine);
// const [defaultFiltered, setDefaultFiltered] = useState("")
const updateOnlineStatus = () => {
const online = navigator.onLine;
setIsOnline(online);
setEventAlert(online ? "" : "Currently viewing Offline Database");
};
useEffect(() => {
updateOnlineStatus();
fetchData();
window.addEventListener("online", updateOnlineStatus);
window.addEventListener("offline", updateOnlineStatus);
return () => {
window.removeEventListener("online", updateOnlineStatus);
window.removeEventListener("offline", updateOnlineStatus);
};
}, [currentCity, currentNOE]);
const fetchData = async () => {
const allEvents = await getEvents();
const filteredEvents = currentCity === "See all cities" ?
allEvents :
allEvents.filter(event => event.location === currentCity)
setEvents(filteredEvents.slice(0, currentNOE));
setAllLocations(extractLocations(allEvents));
}
return (
<div className="App">
<h1 data-testid="outside-element">Meetup App</h1>
<img className="time" alt="meet-logo" src='/calendar.png'></img>
<div className="cityError-Message">
{eventAlert.length ? <EventAlert text={eventAlert}/> : null}
</div>
<CitySearch
allLocations={allLocations}
setCurrentCity={setCurrentCity}
setCityAlert={setCityAlert}
/>
<div className="cityError-Message">
{cityAlert.length ? <CityAlert text={cityAlert}/> : null}
</div>
<NumberOfEvents
currentNOE={currentNOE}
setCurrentNOE={setCurrentNOE}
setNumberAlert={setNumberAlert}
/>
<div className="cityNumber-Message">
{numberAlert ? <NumberAlert text={numberAlert}/> : null}
</div>
<div className="charts-container">
<EventGenresChart
events={events}
/>
<CityEventsChart
allLocations={allLocations}
events={events}
/>
</div>
<EventList
events={events}
setEventAlert={setEventAlert}
/>
</div>
);
}
export default App;
import mockData from './mock-data';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css'; // Include the CSS for styling
export const getAccessToken = async () => {
const accessToken = localStorage.getItem('access_token');
const tokenCheck = accessToken && (await checkToken(accessToken));
if (!accessToken || tokenCheck.error) {
await localStorage.removeItem("access_token");
const searchParams = new URLSearchParams(window.location.search);
const code = await searchParams.get("code");
if (!code) {
const response = await fetch(
"https://pp3rdn62qf.execute-api.us-east-1.amazonaws.com/dev/api/get-auth-url"
);
const result = await response.json();
const { authUrl } = result;
return (window.location.href = authUrl);
}
return code && getToken(code);
}
return accessToken;
};
export const extractLocations = (events) => {
const extractedLocations = events.map((event) => event.location);
const locations = [...new Set(extractedLocations)];
return locations;
};
const checkToken = async (accessToken) => {
const response = await fetch(
`https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${accessToken}`
);
const result = await response.json();
return result;
};
const removeQuery = () => {
let newurl;
if (window.history.pushState && window.location.pathname) {
newurl =
window.location.protocol +
"//" +
window.location.host +
window.location.pathname;
window.history.pushState("", "", newurl);
} else {
newurl = window.location.protocol + "//" + window.location.host;
window.history.pushState("", "", newurl);
}
};
export const getEvents = async () => {
if (window.location.href.startsWith("http://localhost")) {
return mockData;
}
if (!navigator.onLine) {
const events = localStorage.getItem("lastEvents");
NProgress.done();
return events?JSON.parse(events):[];
}
const token = await getAccessToken();
if (token) {
removeQuery();
const url = "https://pp3rdn62qf.execute-api.us-east-1.amazonaws.com/
dev/api/get-calendar- events" + "/" + token;
const response = await fetch(url);
const result = await response.json();
if (result) {
NProgress.done();
localStorage.setItem("lastEvents", JSON.stringify(result.events));
return result.events;
} else {
return null;
}
};
return null;
};
const getToken = async (code) => {
try {
const encodeCode = encodeURIComponent(code);
const response = await fetch( 'https://pp3rdn62qf.execute-api.us-east-1.amazonaws.com/dev/api/token' + '/' + encodeCode);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const { access_token } = await response.json();
access_token && localStorage.setItem("access_token", access_token);
return access_token;
} catch (error) {
error.json();
}
}
src/components/CityEventsChart.jsx
import React, { useState, useEffect } from 'react';
import {
ScatterChart,
Scatter,
XAxis, YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
Label
} from 'recharts';
const CityEventsChart = ( {allLocations, events} ) => {
const [data, setData] = useState([]);
const getData = () => {
const data = allLocations.map((location) => {
const countnumber = events.filter((event) => event.location === location).length
const city = location.split((/, | - /))[0]
return { city, countnumber };
})
return data;
};
useEffect(() => {
setData(getData());
}, [JSON.stringify(events)]);// this works consistantly
// [`${events}`]) SAME NUMBER OBJECTS = SAME ARRAY WHEN VALUE SHOULD OF CHANGED CF PLEASE INVESTIGATE
return (
<ResponsiveContainer width="50%" height={400}>
<ScatterChart
margin={{
top: 100,
right: 50,
bottom: 100,
left: 40,
}}
>
<CartesianGrid />
<XAxis type="category" dataKey="city" name="City"
angle={60} interval={0} tick={{ dx: 20, dy: 40, fontSize: 14 }} >
<Label value="Cities" offset={-70} position="insideBottom" />
</XAxis>
<YAxis type="number" dataKey="countnumber" name="Meetings" allowDecimals={false} >
<Label value="Number of Meetings" offset={-120} position="insideLeft" />
</YAxis>
<Tooltip cursor={{ strokeDasharray: '3 3' }} />
<Scatter name="A school" data={data} fill="#8884d8" />
</ScatterChart>
</ResponsiveContainer>
);
}
export default CityEventsChart;
Event Genres Chart
import React from 'react';
import { PieChart, Pie, Cell, ResponsiveContainer, Legend } from 'recharts';
import { useEffect, useState} from 'react';
const EventGenresChart = ({ allLocations, events }) => {
const [data, setData] = useState([]);
const genres = ['React', 'JavaScript', 'Node', 'jQuery', 'Angular' ];
const COLORS = ['#0088FE','#00C49F','#FFBB28','#FF8042','#8884d8'];
const getData = () => {
const data = genres.map((genre) => {
const filteredEvents = events.filter(event => event.summary.includes(genre));
return {
name: genre,
value: filteredEvents.length
}
})
return data;
};
useEffect(() => {
setData(getData());
}, [JSON.stringify(events)]);// this works consistantly
// [`${events}`]) SAME NUMBER OBJECTS = SAME ARRAY WHEN VALUE SHOULD OF CHANGED CF PLEASE INVESTIGATE
return (
<ResponsiveContainer width="50%" height={400}>
<PieChart width={400} height ={400} >
<Pie
data={data}
labelLine={false}
outerRadius={125}
fill="#8884d8"
dataKey="value"
label="name"
>
{data.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
<Legend verticalAlign="bottom" height={36}/>
</PieChart>
</ResponsiveContainer>
);
}
export default EventGenresChart;
City Search component
import React from 'react';
import { useState, useEffect, useRef } from "react";
import '../../src/App.css';
const CitySearch = ({ setCurrentCity, allLocations,
setCityAlert}) => {
const [showSuggestions, setShowSuggestions] = useState(false);
const [suggestions, setSuggestions] = useState(allLocations);
const SuggestionListRef = useRef(null);
const [eventAlert, setEventAlert] = useState("");
const [query, setQuery] = useState("");
const handleInputChanged = (event) => {
const value = event.target.value;
setQuery(value); // Always reflect what's typed
const trimmedValue = value.trim();
// Filter suggestions based on input
const filteredSuggestions = allLocations.filter((location)
=>
location.toLowerCase().includes(trimmedValue.toLowerCase())
);
if (filteredSuggestions.length === 0) {
setSuggestions([]);
setCityAlert("We can not find the city you are looking
for. Please try another city.");
} else {
setSuggestions(filteredSuggestions);
/// setCityAlert(""); CANNOT FIND FUNCTION DON'T GET
WHY!!!!
}
setShowSuggestions(true);
};
const handleItemClicked = (event) => {
const value = event.target.textContent;
setQuery(value);
setShowSuggestions(false); // Hide suggestions
setCurrentCity(value);
setEventAlert("");
};
// Handle clicking outside the dropdown
useEffect(() => {
const handleClickOutside = (event) => {
if (SuggestionListRef.current &&
!SuggestionListRef.current.contains(event.target)) {
setShowSuggestions(false); // Hide suggestions when
clicking outside
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown",
handleClickOutside);
};
}, [`${allLocations}`]);
return (
<div id="citySearch" data-testid="city-search">
<h3>Choose your nearest city</h3>
<input
type="text"
className="city"
placeholder="Search City for meetings"
value={query}
onFocus={() => {
if (query.trim() === "") {
setSuggestions(allLocations);
}
setShowSuggestions(true);
}}
onChange={handleInputChanged}
data-testid="search-input"
/>
{showSuggestions && (
<ul data-testid="CityList" className="suggestions" ref=.
{SuggestionListRef}>
<div className="listCities">
{suggestions.map((suggestion, index) => (
<li
className="cityName"
onClick={handleItemClicked}
key={`${suggestion}-${index}`}
>
{suggestion}
</li>
))}
<li
className="cityName"
key="See all cities"
onClick={handleItemClicked}
>
<b>See all cities</b>
</li>
</div>
</ul>
)}
</div>
);
};
export default CitySearch;
Number of Events Component
import React from 'react';
import { useState } from "react";
import '../App.css';
const NumberOfEvents = ({setCurrentNOE, currentNOE,
setNumberAlert }) => {
const handleInputChanged = (event) => {
const value = event.target.value;
// Convert value to a number if it's a valid numeric
string
const numericValue = Number(value);
setCurrentNOE(value);
// Validate the input
if (isNaN(numericValue)) {
setNumberAlert('Enter a valid number');
} else if (numericValue < 1) {
setNumberAlert('Number must be greater than 0');
} else if (!Number.isInteger(numericValue)) {
setNumberAlert('Input must be a whole number')
}
else
{
setNumberAlert('');
setCurrentNOE(numericValue); // Update the main
state only when input is valid
}
};
return (
<div id="numberSearch" data-testid="number-of-events">
<h5>Number of Events:</h5>
<input
className="eventNumber"
type="number"
value={currentNOE}
role="textbox"
onChange={handleInputChanged}
data-testid="NumberOfEventsInput"
/>
</div>
);
};
export default NumberOfEvents;
src/components/EventList.js
import React from 'react';
import Event from "./Event";
import '../../src/App.css';
const EventList = ({ events }) => {
return (
<ul className="event-list" data-testid="event-list">
{events ?
events.map(event => <Event key={event.id} event={event}
/>) :
null}
</ul>
);
}
export default EventList;
src/components/Event.js
import React from 'react';
import Button from './Button'
import '../../src/App.css';
const Event = ({ event }) => {
return (
<li className="event">
<h3>{event.summary}</h3>
<p className="eventAttribute">{event.start.dateTime}</p>
<p className="eventAttribute">{event.end.dateTime}</p>
<p className="eventAttribute">{event.location}</p>
<Button event={event} />
</li>
);
}
export default Event;