My third challenge for the Vanilla JS Academy was to build a random quotes app using an API. Let’s look at how I approached the task.
- Fetching and displaying quotes
- Avoiding duplicate quotes
- Wrapping up
Fetching and displaying quotes
The HTML and CSS
Before we do anything, let’s look at the markup I started with.
The most noteworthy part is the
.screen-reader element. It’s visually hidden, but still accessible to screen readers. Here’s the CSS that makes this work (borrowed from my friend Chris Ferdinandi, who actually runs the Academy):
aria-live attribute is even more important. It informs screen readers that the content of the element will change, and that they should therefore pay attention to it and announce the changes.
There’s a reason why I created an empty
div for this rather than just add the
aria-live attribute directly to the
#app element. The attribute only seems to work when you’re inserting plain text—not HTML. When the time comes to add the quote to the page, I will first update the
innerHTML of the
#app element, and then the
textContent of the
This feels like a bit of a hack, so if any accessibility experts have a better solution, please let me know!
Immediately-invoked function expression
As with most of my projects, I started by creating an immediately-invoked function expression (also known as an IIFE). This allowed me to keep my code outside the global scope and minify my references to the
document. I also added the
"use strict" statement so I could opt into strict mode.
I planned to use Promises and the Fetch API for this project, both of which are well supported in modern browsers, but not in older versions. To resolve this issue, I added the following script element just before the closing
</body> tag in my HTML, and before my own script element:
This adds the default bundle of polyfills from polyfill.io, and one for the Fetch API. A polyfill is
a snippet of code that adds support for a feature to browsers that don’t offer it natively (thanks for the succinct definition, Chris!).
The great thing about polyfill.io is that it only returns polyfills to browsers that need them. The latest version of Chrome will get nothing, but older browsers will get the code they need to make the requested APIs work. Neat, right?
For more on polyfills, please check out Everything you ever wanted to know about polyfills on The Vanilla JS Podcast.
The first thing I did inside my IIFE was create my variables. I added one for the API endpoint (so I could easily change it later), and three for the elements I needed from the DOM:
Next, I created some functions to pass into the
catch() methods. These methods get chained onto my call to the
fetch() function. They run if the Promise on which they were called was resolved or rejected, respectively.
To learn about the Fetch API, please check out Chris’ post How to use the Fetch API with vanilla JS.
Get the JSON data
Before I could do anything with the Fetch request, I needed to get the data in JSON format. I did that by calling the
json() method on the
If the request was unsuccessful, though, I needed to reject the Promise. I did that using the static
Promise.reject() method, passing in the
I used the ternary operator here to make my code more concise. If the
response was successful (i.e. its
ok property was
true), I returned the JSON data; if not, I returned a rejected Promise.
Insert the quote
If the call to the
getJSON() function is successful, it returns an array containing a single Ron Swanson quote as a string. For example:
I created a function,
insertQuote(), to add the quote to the DOM and announce the change to screen readers. The
data parameter refers to the JSON data returned by the
The reason I didn’t need to actually retrieve the quote from the array using
This is called type coercion. In hindsight, I think it would have been better to be more explicit and use
data. It’s just a better practice because it avoids unexpected behaviour and errors.
Next up, I created a function,
insertError(), to pass into the
catch() method. It adds an error message to the DOM if there is a problem anywhere along my chain of Promises.
It’s pretty much indentical to my
insertQuote() function. Were I to revisit this project, I would probably refactor these functions into one to make my code more DRY (Don’t Repeat Yourself) and remove a bit of redundancy.
Wrapping all this into a single function
The last function I created wraps all of this into a single function called
fetchQuote(). This was necessary because I wanted to able to run these steps not only when the page loads, but also when the user presses the
Show Another Quote button. Using a single function avoids duplicate code.
Inits and event listeners
The final step was to initialise the app and add an event listener to the
Show Another Quote button. I simply called my
fetchQuote() function on page load and used it as the callback function for my click handler.
With that, I had a working app! 🙌
Avoiding duplicate quotes
The problem with the existing solution is that when the user clicks the
Show Another Quote button, the API might return the same quote. The change would be too quick for the user to see, so it would look like nothing happened.
To fix this, I modified the project so that if the same quote gets returned from the API in the last 50 quotes, I skip it and fetch another one instead.
Updating the polyfill bundle
I decided to add an additional polyfill for the
Array.prototype.includes() method (which otherwise has no IE support). You’ll see why shortly. Here’s the updated polyfill bundle:
I could have used the
Array.prototype.indexOf() method instead, which would have removed the need for this polyfill, but I think the
includes() method is more readable.
Adding an array of past quotes
Once I added the polyfill, I created a new variable called
pastQuotes. I added it to the top of my script with the rest of my variables. It’s just an empty array to which I will add the the most recent 50 quotes.
Checking the quote
I then added a function,
checkQuote(), to check the validity of the current quote. If it was among the last fifty quotes, I use recursion to get a new one. Otherwise, I add it to the
pastQuotes array. I then return it as a resolved Promise using the static
Updating the chain of Promises
The final step was to simply add my new
checkQuote() function to the chain of Promises. Here’s the modified
With that, I have a working app that gets random quotes from an API while avoiding duplicates! 💯
If you’d like, you can view the demo on GitHub Pages or check out the source code on GitHub. Everything is available under the MIT License.
If you want to learn how to build cool apps like this, I highly recommend you join the next session of The Vanilla JS Academy. Chris is such a good guy and he will look after you! ❤️
If you have questions, feedback, or any other suggestions, please do email me. I'd love to hear from you!