DEV Community

Cover image for Perfect Date: A Javascript and API Project
Christine Contreras
Christine Contreras

Posted on

Perfect Date: A Javascript and API Project

I am currently a Flatiron student. Our first phase focused on the fundamentals of Javascript and understanding APIs. At the end of this phase, we have to synthesize our learnings into a one-page app that interests us and showcases our understanding. I want to share an overview of my project as well as the two parts of the project that I struggled with but ultimately learned the most from.

What is an API and Why They Are Amazing

Before I get into my project, let’s break down what an API is. API stands for “Application Programming Interface” and is an intermediary between two applications so that they can communicate with each other.

Companies (like Google) or organizations (like Governments) use API’s to share information or functionality with us so we can use the data to build projects. There are free API’s out there open to anybody who wants to use them.

For this project, I used the Weatherstack API. They have a free version where you can view current weather but I paid to use their historical weather database. API’s make it easy for you to use by providing documentation on how to access the data and what data is available for use.

Perfect Date Overview

I am currently in the beginning stages of planning a wedding and one of the main parts of a wedding you can’t control is the weather. But I can tilt the odds in my favor by picking a wedding date that has had historically good weather. That is where the concept for the Perfect Date app came from. It lets you plan big events, whether that’s a wedding or an awesome vacation, around historical weather data.

See Average Weather Data By Location and Month

The first part of my app is the form. You input your location, month, and timeframe you want to see weather data for. For the time frame, you can pick from last year through the past 5 years of weather data.
See Average Weather Data By Location and Month

Once the API request is made a calendar view of the averages is created. The example below shows the average data for Miami from 2021-2020. For the icons and weather description, it is showing the mode for the aggregated data. If there is no mode then it shows the most recent icon and weather description.
Calendar View of Weather Data

See Breakdown Of Weather Date For A Specific Day

If you click “see details” on an individual day you can see the breakdown of weather data by year.
See Breakdown Of Weather Date For A Specific Day

Save Your Dates and Compare

If you click “save this date” it will save the data into a JSON server (a local one that I set up). Click “see all saved dates” and it will break down your saved dates by location. If you save multiple dates and locations it will break down the dates by location.
Save Your Dates and Compare

App Problems and Solutions

I thought about this app 24/7 for a week straight and how to make it work when I ran into a problem. This included jolting up in the middle of the night with solutions and writing them down so I can try them first thing in the morning. Two problems came up with this application that made me want to cry at times because I hadn’t learned about the solution in Flatiron yet.

Problem #1: Grouping Data Together From API

The first challenge I ran into was that the Weatherstack API only lets you fetch historical weather data 60 days at a time. That meant that depending on how many years of data you want to compare I had to make a separate fetch to the API for each year. So instead of averaging the data once it’s called from the API I needed to store and group the data accurately from each API call before I could average the data.

The second part of this problem was grouping the dates accurately. I didn’t want to group each 1st of March together but each first Friday of the month together. They are not one in the same since dates move each year by one day or more depending on whether it’s a leap year.

My Solution

I decided to create an array for each combination of week and day of the week in a month. Depending on the month you are looking at and in what year, the max amount of weeks to show on a calendar are 6 weeks. With 7 days in a week, it came out to 42 combinations. (Although I try to avoid declaring variables with var, in this case, it was the only way I could declare the variables and they still work for my solution).

//create arrays to push API data. array push corresponds with week and day of week the object falls on
Var [week1Day0,week1Day1,week1Day2,week1Day3,week1Day4,week1Day5,week1Day6,week2Day0,week2Day1,week2Day2,week2Day3,week2Day4,week2Day5,week2Day6,week3Day0,week3Day1,week3Day2,week3Day3,week3Day4,week3Day5,week3Day6,week4Day0,week4Day1,week4Day2,week4Day3,week4Day4,week4Day5,week4Day6,week5Day0,week5Day1,week5Day2,week5Day3,week5Day4,week5Day5,week5Day6,week6Day0,week6Day1,week6Day2,week6Day3,week6Day4,week6Day5,week6Day6] = [[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]];
Enter fullscreen mode Exit fullscreen mode

I then created two functions to help me figure out the week and day of the week a day falls on with the parameters of the month, year, day.

//day of week
function dayOfWeek (month, year, day) {
   let dayOfWeek = new Date(year, month, day).getDay();
   return dayOfWeek;
}

// week of month
function weekOfMonth (day, start) {
   return Math.ceil((day + start) / 7);
}
Enter fullscreen mode Exit fullscreen mode

When I fetch from the API the date pulled in is formatted as a string in the format below.

{
"historical": {
        "2021-03-01": {
            "date": "2021-03-01",
            "date_epoch": 1614556800,
            "astro": {
                "sunrise": "07:57 AM",
                "sunset": "07:30 PM",
                "moonrise": "10:18 PM",
                "moonset": "09:41 AM",
                "moon_phase": "Waning Gibbous",
                "moon_illumination": 79
            },
            "mintemp": 50,
            "maxtemp": 55,
            "avgtemp": 54,
            "totalsnow": 0,
            "sunhour": 7.3,
            "uv_index": 3,
            "hourly": [...]
}
}
Enter fullscreen mode Exit fullscreen mode

I needed to break up the date so I could then add it into my functions. Once the string was split, I was able to find the week and day of the week the date fell on. Then, I had to push the date into the accurate array. It was difficult for me to find online how to create and push data into a dynamic variable name. As stated above, the only method that worked was to set the initial arrays to vars instead of const and then push the date objects into each array using window.

Ideally, I would have liked to not declare the empty arrays and just create each new empty array with a dynamic variable name as they came up. I could not figure out how to do that so this was my solution.

//push each date into the right array
for(const date in datesObject) {
   const data = datesObject[date];

    let dateArray = data.date.split('-');
    let dateDay = parseInt(dateArray[2]);

    let dateMonth = parseInt(dateArray[1]) - 1;
    let dateDayOfWeek = dayOfWeek(dateMonth, dateArray[0], dateArray[2]);
    let dateWeekOfMonth = weekOfMonth(dateDay, startDay);

               window[`week${dateWeekOfMonth}Day${dateDayOfWeek}`].push(data);

}
Enter fullscreen mode Exit fullscreen mode

Problem #2: Waiting For API data to Push to Arrays Before Creating Averages

Typically Javascript runs synchronously and in a single thread. That means that it waits for the code above it to finish before moving onto the next line of code. However fetch is asynchronous. This means that Javascript doesn’t wait for an API fetch to be done before it runs the next line of code. This is typically a good thing for a website since you don’t know how long and if a fetch will come back and you don’t want a user waiting forever for the page to load. However, this project is solely about fetching API data and relies 100% on the API data.

At first, the fetch request to the API would be sent but then my calendar would be created with blank li’s because it would continue running my code without the API data in the corresponding arrays. Essentially I needed to stop the rest of my code from running and only continue once I fetched the API data.

My Solution

The solution I found online was async functions. I will not try to claim all knowledge about async functions but found this video helpful in explaining async functions and how to create them. Essentially async functions let you use the keyword await. This lets the function “wait” for a promise to be given back before it runs the next line of code. This means I had to wrap my fetchSubmit and createDatesInCalendar functions each in a promise and pass them back into my async function. Once I created the async function it worked as intended!

//example of my createDatesInCalendar function wrapped in a promise
function createDatesInCalendar() {
   return new Promise(resolve => {
       var dayOfMonthArrays = [week1Day0,week1Day1,week1Day2,week1Day3,week1Day4,week1Day5,week1Day6,week2Day0,week2Day1,week2Day2,week2Day3,week2Day4,week2Day5,week2Day6,week3Day0,week3Day1,week3Day2,week3Day3,week3Day4,week3Day5,week3Day6,week4Day0,week4Day1,week4Day2,week4Day3,week4Day4,week4Day5,week4Day6,week5Day0,week5Day1,week5Day2,week5Day3,week5Day4,week5Day5,week5Day6,week6Day0,week6Day1,week6Day2,week6Day3,week6Day4,week6Day5,week6Day6]

       dayOfMonthArrays.forEach(day => {
           let id;

           //if array is empty create empty list item else create list item with array info
           if (day.length === 0){
               id = dayOfMonthArrays.indexOf(day);
               emptyLi(id);
           } else {
               let newObject = {};
               id = dayOfMonthArrays.indexOf(day);
               createAverageObjects(day, newObject, id);
           }

           //save arrays with API objects into new array so we can access data later
           return dateArrayObject.push(day);
       });

       resolve('day averages function finished');

   });

}

//how many times to fetch API data (fetch for each year of data). wait for fetches to be done before calculating averages
   if(timeframeIndex === 0){
       async function getData(){
           await fetchSubmit(locationInput, monthNum, monthIndex, year1, numDays);
           await createDatesInCalendar();
       }
       getData();

   } else if(timeframeIndex === 1){
       async function getData(){
           await fetchSubmit(locationInput, monthNum, monthIndex, year1, numDays);
           await fetchSubmit(locationInput, monthNum, monthIndex, year2, numDays);
           await createDatesInCalendar();
       }

       getData();

   } else if(timeframeIndex === 2) {
       async function getData(){
           await fetchSubmit(locationInput, monthNum, monthIndex, year1, numDays);
           await fetchSubmit(locationInput, monthNum, monthIndex, year2, numDays);
           await fetchSubmit(locationInput, monthNum, monthIndex, year3, numDays);
           await createDatesInCalendar();
       }

       getData();

   } else if(timeframeIndex === 3){
       async function getData(){
           await fetchSubmit(locationInput, monthNum, monthIndex, year1, numDays);
           await fetchSubmit(locationInput, monthNum, monthIndex, year2, numDays);
           await fetchSubmit(locationInput, monthNum, monthIndex, year3, numDays);
           await fetchSubmit(locationInput, monthNum, monthIndex, year4, numDays);
           await createDatesInCalendar();
       }

       getData();

   } else {
       async function getData(){
           await fetchSubmit(locationInput, monthNum, monthIndex, year1, numDays);
           await fetchSubmit(locationInput, monthNum, monthIndex, year2, numDays);
           await fetchSubmit(locationInput, monthNum, monthIndex, year3, numDays);
           await fetchSubmit(locationInput, monthNum, monthIndex, year4, numDays);
           await fetchSubmit(locationInput, monthNum, monthIndex, year5, numDays);
           await createDatesInCalendar();
       }

       getData();
   }

Enter fullscreen mode Exit fullscreen mode

Final Thoughts

Building this app was a great challenge and I loved doing it. I would like to make improvements to the app, such as organizing the saved dates page more and being able to select saved dates to compare them side by side.

I am new to Javascript and API’s so if you know a better solution to my struggles please share them in the comments! I feel like the code above could be more DRY and/or shorter and am always looking to improve my coding.

Top comments (2)

Collapse
 
hey_yogini profile image
Yogini Bende

The app idea itself is really interesting! Enjoyed reading this article!
Good work 👏

Collapse
 
momander profile image
Martin Omander

Thanks for sharing your learnings! I enjoyed your article.