Fetching APIs to build Web Applications

Starring The Official Joke API

Table of contents

In between studying frontend, I do projects on the side to test my understanding. This is a tutorial on consuming 'The Official Joke API' and how to generate jokes using JavaScript.

JavaScript provides multiple ways to fetch data asynchronously from an API endpoint.

One of the most popular methods for fetching data asynchronously is the Fetch API. It is a modern way to handle HTTP requests, which allows you to make requests and handle responses with promises. Using the Fetch API, you can fetch data from an API endpoint in just a few lines of code:

fetch('https://official-joke-api.appspot.com/jokes/random')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error))

The above code fetches a random joke from the API endpoint and logs the response data in the console. The fetch() method returns a promise that resolves to a Response object. To extract the response data, we need to call the json() method on the response object, which also returns a promise that resolves to the actual data.

Another method for fetching data asynchronously is using the XMLHttpRequest (XHR) object. The XHR object has been used to make asynchronous HTTP requests for a long time, and it is still widely used in legacy codebases. Here's an example of how to fetch data using XHR:

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://official-joke-api.appspot.com/jokes/random');
xhr.onload = function() {
  if (xhr.status === 200) {
    const data = JSON.parse(xhr.responseText);
    console.log(data);
  } else {
    console.error('Error:', xhr.status);
  }
};
xhr.onerror = function() {
  console.error('Error:', xhr.status);
};
xhr.send();

The above code creates a new XHR object, sets the HTTP method and URL, and defines the onload and onerror callback functions to handle the response data and errors, respectively. When the request completes successfully, the onload function extracts the response data using the JSON.parse() method and logs it in the console. If the request fails, the onerror function logs the error status in the console.

A third way to fetch data asynchronously is to use the Axios library. Axios is a popular promise-based HTTP client for JavaScript that provides a simple and elegant API for making HTTP requests. Here's an example of how to fetch data using Axios:

axios.get('https://official-joke-api.appspot.com/jokes/random')
  .then(response => console.log(response.data))
  .catch(error => console.error(error));

The above code uses the axios.get() method to make an HTTP GET request to the API endpoint and logs the response data in the console. The then() method is called on the returned promise to extract the response data, and the catch() method is called to handle errors.

Now that we're all familiar with the ways to fetch data from an API endpoint asynchronously, let's use fetch to create a Joke Generator.

<form class="container" id="joke-form">
  <div class="no-of-jokes-contain">
    <label for="jokes" class="Joke-label" id="label-text">Select number of jokes</label>
    <select name="no-of-jokes" id="selection" class="jokes-no">
      <option value="1" id="option1">1</option>
      <option value="3" id="option3">3</option>
      <option value="5" id="option5">5</option>
      <option value="10" id="option10">10</option>
    </select>
  </div>
  <span class="alert" id="notice"></span>
  <button id="button" type="button" class="get-joke">Get Joke</button>
</form>
<section class="jokes-section">
  <li class="set-up">
    <p class="joke"></p>
    <p class="punchline"></p>
    <p class="error"></p> 
  </li>
  <!-- additional list items with class "set-up" -->
</section>

In this HTML code, the most important parts that can be accessed with JavaScript are:

  1. The select element with id "selection" allows the user to choose the number of jokes to display.

  2. The button element with id "button" triggers the display of jokes when clicked.

  3. The section element with class "jokes-section" contains the list items with class "set-up". Each list item contains a joke, a punchline, and an error element. These elements can be updated dynamically with JavaScript to display the retrieved jokes and error messages.

Now let's declare our variables with JavaScript.

First, we define constants for each HTML element we will be interacting with using the document.getElementById or document.getElementsByClassName methods.

const btn = document.getElementById('button');

const select = document.getElementById('selection');

const label = document.getElementById('label-text');

const form = document.getElementById('joke-form');

const jokes = document.getElementsByClassName('joke');

const punchLine = document.getElementsByClassName('punchline');

const errorMessage = document.getElementsByClassName('error');

const alert = document.getElementById('notice');

Then, we add an event listener to the button element using the addEventListener method. This will listen for a click event and then call the showJoke or showError functions.

button.addEventListener('click', (e) => {
    e.preventDefault();
    let numberOfJokes = Number(select.options[select.selectedIndex].value);
    let currentJokeNumber = numberOfJokes;
    fetch('https://official-joke-api.appspot.com/jokes/ten').then((response) => response.json()).then((data) => showJoke(data, currentJokeNumber)).catch(() => showError());
})

The showJoke function takes in two parameters - data, which is the response data from the joke API, and currentJokeNumber, which is the number of jokes to display. The function sets jokesCalled to true and then calls highlightLabel and refreshJokes functions.

function showJoke(data, currentJokeNumber) {
    let jokesCalled = true;
    highlightLabel(currentJokeNumber);
    refreshJokes(currentJokeNumber, jokesCalled);
    for (let i = 0; i < currentJokeNumber; i++) {
        jokes[i].style.display = 'block';
        jokes[i].innerText = data[i].setup;
        setTimeout(() => {
            punchLine[i].style.display = 'block';
            punchLine[i].innerText = data[i].punchline;
        }, 3000);
    }
}

The refreshJokes function takes in currentJokeNumber and jokesCalled as parameters. It loops through each joke element and sets its text and display to none. If jokesCalled is false, then the function will not display any jokes. Otherwise, the function will wait 5200 * currentJokeNumber milliseconds before hiding the jokes and punchlines.

function refreshJokes(currentJokeNumber, jokesCalled) {
    for (let i = 0; i < currentJokeNumber; i++) {
        jokes[i].innerText = '';
        jokes[i].style.display = 'none';
        punchLine[i].innerText = '';
        punchLine[i].style.display = 'none';
        if (jokesCalled === false) {
            jokes[i].innerText = '';
            jokes[i].style.display = 'none';
            punchLine[i].innerText = '';
            punchLine[i].style.display = 'none';
        } else {
            setTimeout(() => {
                jokes[i].innerText = '';
            jokes[i].style.display = 'none';
            punchLine[i].innerText = '';
            punchLine[i].style.display = 'none';
            }, 5200 * currentJokeNumber)
        }
    }
}

Now we can hide the jokes after rough 5 seconds( multiplied by the number of jokes requested). We now need a way to show errors for failed responses from the API and an alert for a successful response.

function highlightLabel(currentJokeNumber) {
    label.style.color = 'hsl(53, 97%, 58%)';
    label.style.transition = 'all .4s';
    setTimeout(() => {
    label.style.color = 'hsl(100, 0%, 100%)';
    }, 1000);
    jokeCounter(currentJokeNumber);
}

function jokeCounter(currentJokeNumber) {
    notice.style.display = 'inline';
    notice.style.backgroundColor = 'hsl(133, 89%, 48%)';
    notice.innerText = `${currentJokeNumber} new joke(s)`;
    setTimeout(() => {
        notice.style.display = 'none';
    }, 3000);
}

function showError() {
    notice.innerText = 'No Jokes Pal!';
    notice.style.backgroundColor = 'hsl(0, 100%, 50%)';
    notice.style.display = 'inline';
    errorMessage[0].style.display = 'block'
    errorMessage[0].innerText = 'Sorry, request for joke failed!';
        setTimeout(() => {
            errorMessage[0].innerText = '';
            errorMessage[0].style.display = 'none';
        }, 8000)
    console.log('sorry there was an error somewhere');
}

The highlightLabel function takes in one parameter, currentJokeNumber, which represents the current joke number. This function is called when a new joke is fetched from an API and displayed on a web page. The purpose of this function is to highlight the label that shows the number of new jokes.

The function starts by setting the color of the label to a shade of green using the HSL color model. This change is applied to the label using the style property of the label element.

Next, the function sets a transition property on the label element to create a smooth transition effect when the color of the label changes back to its original color.

After that, the function uses the setTimeout method to delay the execution of a callback function that will change the color of the label back to its original color after one second. The callback function sets the color of the label back to white using the same HSL color model.

Finally, the function calls the jokeCounter function and passes in the currentJokeNumber as a parameter. The jokeCounter function is responsible for displaying a notice that shows the number of new jokes that have been fetched.

We now have a web app that can fetch any wanted number of jokes from our api and display errors if it encounters any.

With that we're done. Live Site.

Check out my code on My GitHub.

Update -

23/03/2023,

Added clearTimeout to showJokes() function to remove the conflict of old jokes setTimeout affecting new jokes.