π¦ Vanilla JavaScript
Definition
Simply put, Vanilla JavaScript is just bare-bones JavaScript. No libraries, no frameworks, just good old plain JavaScript.
View on Frameworks
[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.
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 });1112const 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 = '');34const combineUserData = ({ mockedUsers, mockedUsersFavFruits }) => {5 const combinedUserData = mockedUsers.reduce((accumulator, currentItem) => {6 const usersFavFruit = mockedUsersFavFruits.find(7 ({ id }) => id === currentItem.id8 );9 return [{ ...currentItem, ...usersFavFruit }, ...accumulator];10 }, []);11 return combinedUserData;12};1314const 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};2223const renderLoader = (element) => {24 clearElementContent(element);25 writeToDom({26 element,27 output: '<p>β Loading... </p>',28 });29};3031const onClickHandler = async () => {32 const element = document.getElementById('output');33 renderLoader(element);3435 const [mockedUsers, mockedUsersFavFruits] = await Promise.all([36 getMockedUsers(),37 getMockedUsersFavFruits(),38 ]);3940 const data = combineUserData({ mockedUsers, mockedUsersFavFruits });41 const output = createUserOutput(data);4243 clearElementContent(element);44 writeToDom({ output, element });45};4647// index.html48<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 */23// 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 β€οΈ" };67// 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 β€οΈ" };1011// We can handle just like a normal promise with .then()12theJoeCodes().then((value) => console.log(value));1314// Or15theJoeCodes().then(console.log)1617/* --------------------- */1819/* await keyword */20async function theJoeCodes() {21 // Must be used inside async keyword22 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 promise26 // Returns a fulfilled promise with string value27 return response;28};2930// Promise returned that we can handle & pass to window.alert method31theJoeCodes().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/1bc31aa397a0c9fef251a84797d3f50023const getData = (url, options = {}) =>4 fetch(url, options)5 .then((response) => response.json())6 .catch((error) => console.error({ error }));78const onClickHandlerFetchAPI = async () => {9 const element = document.getElementById('output-fetch-api');10 renderLoader(element);1112 const [randomUserOne, randomUserTwo] = await Promise.all([13 getData('https://randomuser.me/api/'),14 getData('https://randomuser.me/api/'),15 ]);1617 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}`;1920 const output = `21 <p>${randomUserOneName}</p>22 <p>${randomUserTwoName}</p>23 `;2425 clearElementContent(element);26 writeToDom({ output, element });27};2829// index.html30<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 2026Cheat sheet to help you master working with JS strings.
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 2026An introduction into the world of JavaScript promises.