🍦 Vanilla JavaScript

Definition

Simply put, Vanilla JavaScript is just bare-bones JavaScript. No libraries, no frameworks, just good old plain JavaScript.

View on Frameworks

Tip

[tl;dr] Frameworks are awesome, but make sure to learn the language powering it first.

JavaScript is the programming language of the web. The majority of us working with client-side JavaScript do so through the patterns and interfaces of a major framework. In React we create components, manage state, write to the DOM and trigger re-renders on a daily basis without even thinking about it. Everything is super simple and just "works" like magic out of the box for us, which can really make us feel like JavaScript wizards πŸ§™. That's because frameworks do a great job at abstracting away the complexities and implementation details of vanilla JavaScript in an attempt to make our lives easier as a developer. This is awesome, but it's a double-edged sword.

It's becoming increasingly popular to jump straight into a major JavaScript framework with the majority of industry web development roles scoped to major frameworks. Jumping into and learning a major framework will likely land you a job in the frontend space, whilst encouraging you to learn the ecosystem surrounding modern JavaScript development. However, if you are really trying to stand out from the crowd and progress further in your career it's important to understand and master the language itself, not just the framework. Remember, frameworks come and go all the time, so don't lock yourself in as a 'React developer'.

This is not by any means a rant against using JavaScript frameworks. In fact, I wouldn't advise constructing an entire application in vanilla JavaScript. Frameworks are awesome and they exist for a reason.

Frameworks help abstract complexities, encourage reusability, and by nature promote following a programming paradigm like functional programming. Frameworks also allow us to compose our applications into lots of small reusable chunks of functionality, ship client features faster, and help increase developer velocity. They help to encourage collaboration and bring developers together to create communities. By using the same framework across multiple applications in an organization, developers can easily jump between projects and collaborate with very little time needed to get up to speed.

With this being said, when starting my career, I transitioned into React from PHP and felt it was difficult to understand and fix issues in React land. In hindsight, development would have been so much easier by deep diving into vanilla JavaScript first.

Later in this article we look at data fetching and DOM manipulation in vanilla JS. Here's a quick visual of how async/await works under the hood when fetching data.

execution paused (awaiting)value resolved

Case Study

Data Fetching & DOM Interactions

In this section, we look at fetching data and writing to the DOM with good old vanilla JavaScript. As part of this, we continue from my last article on 🀝 JavaScript Promises.

You can check out the full working example over on codepen:

https://codepen.io/TheJoeCodes/pen/ZEpLXPe

Fetching Mocked Data

First, here's how to create an asynchronous request to return some mock user data.

1const getMockedUsersFavFruits = () =>
2 new Promise((resolve) => {
3 setTimeout(() => {
4 resolve([
5 { id: 'mocked-user-1', favFruit: 'Watermelon', emoji: 'πŸ‰' },
6 { id: 'mocked-user-2', favFruit: 'Strawberry', emoji: 'πŸ“' },
7 { id: 'mocked-user-3', favFruit: 'Apple', emoji: '🍏' },
8 ]);
9 }, 2000);
10 });
11
12const getMockedUsers = () =>
13 new Promise((resolve) => {
14 setTimeout(() => {
15 resolve([
16 { id: 'mocked-user-1', name: 'Sarah' },
17 { id: 'mocked-user-2', name: 'Johnny' },
18 { id: 'mocked-user-3', name: 'MiriΓ£' },
19 ]);
20 }, 2000);
21 });

We've defined two arrow functions that both implicitly return a resolved promised after two seconds. Remember, it might not be exactly two seconds, the promise will resolve when the event loop checks the callstack and pulls from the event queue, see my βš™οΈ JS Runtime Env or πŸ‘» Event Loop Intro articles the event loop to learn more.

Next, we create the onClickHandler() called when the button element is clicked. Let's look at an overview of the functions responsibility.

1const writeToDom = ({ output, element }) => (element.innerHTML += output);
2const clearElementContent = (element) => (element.innerHTML = '');
3
4const combineUserData = ({ mockedUsers, mockedUsersFavFruits }) => {
5 const combinedUserData = mockedUsers.reduce((accumulator, currentItem) => {
6 const usersFavFruit = mockedUsersFavFruits.find(
7 ({ id }) => id === currentItem.id
8 );
9 return [{ ...currentItem, ...usersFavFruit }, ...accumulator];
10 }, []);
11 return combinedUserData;
12};
13
14const createUserOutput = (data) => {
15 let output = '';
16 data.forEach(
17 ({ name, favFruit, emoji }) =>
18 (output += `<p>${name}'s favourite fruit: ${favFruit} ${emoji}</p>`)
19 );
20 return output;
21};
22
23const renderLoader = (element) => {
24 clearElementContent(element);
25 writeToDom({
26 element,
27 output: '<p>βŒ› Loading... </p>',
28 });
29};
30
31const onClickHandler = async () => {
32 const element = document.getElementById('output');
33 renderLoader(element);
34
35 const [mockedUsers, mockedUsersFavFruits] = await Promise.all([
36 getMockedUsers(),
37 getMockedUsersFavFruits(),
38 ]);
39
40 const data = combineUserData({ mockedUsers, mockedUsersFavFruits });
41 const output = createUserOutput(data);
42
43 clearElementContent(element);
44 writeToDom({ output, element });
45};
46
47// index.html
48<button onclick="onClickHandler()">Get Mocked Users πŸš€</button>
49<div id='output'></div>

Overview - onClickHandler()

  • Get the element we want to render each users favourite fruit inside using document.getElementById()
    • This will return an element object representing the element whose id property matches the specified string: 'output'.
  • Call the renderLoader() function to display the loading message.
  • Wait for both asynchronous mocked requests to complete with promise.all() and assign the results as two constants using destructuring.
  • Combine the two data sets together to create a full object representing each user and their favourite color.
  • Pass the combined user data to the createUserOutput() function to generate the output string.
  • Remove the loader component by calling clearElementContent(), passing the output div element.
  • Call the writeToDOM() function with the output and target element as object properties.
    • I usually prefer to pass data around in objects, it allows you to pass arguments in any order without worrying about maintaining the sequential order of the parameters in the function receiving the arguments.
    • Note: "When a node has a child text node that includes the characters (&), (<), or (>), innerHTML returns these characters as the HTML entities" (Source)

Hopefully you noticed the async/await keywords used in the onclick handler. If you're following on from my last article on JavaScript Promises or you've never used this syntax, don't worry. Here's a quick overview.

What is async/await?

These keywords are syntactical sugar for using promises. They can help make asynchronous programming in JavaScript easier to read and reason about.

Using async/await

We use the async keyword before a function declaration to make it asynchronous. This function expects the await keyword inside its scope and knows how to handle it. When we use the async keyword to invoke a function, we are guaranteed to get a promise returned. The await keyword must be used inside a function declared with the async keyword. Here's a commented example:

1/* async keyword */
2
3// When called: theJoeCodes();
4// Will just return the string value: "I hope you're enjoying this article so far ❀️"
5function theJoeCodes() { return "I hope you're enjoying this article so far ❀️" };
6
7// When called: theJoeCodes();
8// Will return a promise: Promise {<fulfilled>: "I hope you're enjoying this article so far ❀️"
9async function theJoeCodes() { return "I hope you're enjoying this article so far ❀️" };
10
11// We can handle just like a normal promise with .then()
12theJoeCodes().then((value) => console.log(value));
13
14// Or
15theJoeCodes().then(console.log)
16
17/* --------------------- */
18
19/* await keyword */
20async function theJoeCodes() {
21 // Must be used inside async keyword
22 const response = await Promise.resolve("I hope you're enjoying this article so far ❀️");
23 // This will console log: {type: "string"}
24 console.log({ type: typeof response });
25 // Remember, when using the async keyword, we are guaranteed our return value is a promise
26 // Returns a fulfilled promise with string value
27 return response;
28};
29
30// Promise returned that we can handle & pass to window.alert method
31theJoeCodes().then(alert);

Using Fetch Web API

In the previous example, we mocked an asynchronous request using promises and setTimeout. Now let's use the browser's built-in Fetch Web API.

What is Fetch?

The fetch API enables us to make network requests similar to XMLHttpRequest (XHR). The key difference being the Fetch API uses Promises under the hood. I'm not going to go into detail on using the fetch api, it would need another article. However, I still wanted to show the previous example without mocked data. For more details on fetch, I would recommend the google developers article: Introduction to fetch()

Fetching Random Users

1// Full gist: https://gist.github.com/JohnnyMcFadden/1bc31aa397a0c9fef251a84797d3f500
2
3const getData = (url, options = {}) =>
4 fetch(url, options)
5 .then((response) => response.json())
6 .catch((error) => console.error({ error }));
7
8const onClickHandlerFetchAPI = async () => {
9 const element = document.getElementById('output-fetch-api');
10 renderLoader(element);
11
12 const [randomUserOne, randomUserTwo] = await Promise.all([
13 getData('https://randomuser.me/api/'),
14 getData('https://randomuser.me/api/'),
15 ]);
16
17 const randomUserOneName = `${randomUserOne.results[0].name.first} ${randomUserOne.results[0].name.last}`;
18 const randomUserTwoName = `${randomUserTwo.results[0].name.first} ${randomUserTwo.results[0].name.last}`;
19
20 const output = `
21 <p>${randomUserOneName}</p>
22 <p>${randomUserTwoName}</p>
23 `;
24
25 clearElementContent(element);
26 writeToDom({ output, element });
27};
28
29// index.html
30<button onclick="onClickHandlerFetchAPI()">Get Random Users πŸš€</button>
31<div id='output-fetch-api'></div>

Final Thoughts

For any new JavaScript developers, hopefully you've learnt something new in this article. The best way to learn JavaScript is to start creating some of your own snippets without a framework or library, or maybe even try create a simple HTML canvas game.

It's important to weigh up the pros and cons of using a framework or library over writing a solution yourself in vanilla JavaScript. Remember, frameworks exist for a reason. We don't want to reinvent the wheel when bulletproof libraries and frameworks exists that have been tried and tested. Don't be afraid of using frameworks or a new library, but consider checking the bundle size of the package you are installing (Bundlephobia is great), you might be better off implementing a smaller sized vanilla implementation yourself.

This article also builds on my previous promises article, so make sure to check it out.

Was this useful?

Thanks for reading! Time for one more?

'The Joe Codes'.split(' ') // ['The', 'Joe', 'Codes']
'The Joe Codes'.includes('Joe') // true

🧡 Strings Cheat Sheet

30 Dec 2020 β€’ πŸ“– 10 min read β€’ Updated 15 Mar 2026
πŸ”° Beginner friendly

Cheat sheet to help you master working with JS strings.

0
0
0
JAVASCRIPT
promiseAddOne(value)
.then(val => promiseMultiplyByTwo(val))
.then(val => promiseDivideByTen(val))
.then(val => console.log(val))

🀝 JavaScript Promises

29 Nov 2020 β€’ πŸ“– 10 min read β€’ Updated 15 Mar 2026
πŸ”° Beginner friendly

An introduction into the world of JavaScript promises.

0
0
0
JAVASCRIPT