Tari Ibaba

Tari Ibaba is a software developer with years of experience building websites and apps. He has written extensively on a wide range of programming topics and has created dozens of apps and open-source libraries.

How to Capitalize the First Letter of Each Word in a String in JavaScript

To capitalize the first letter of each word in a string in JavaScript:

  1. Split the string into an array of words with .split('').
  2. Iterate over the words array with .map().
  3. For each word, return a new word that is an uppercase form of the word’s first letter added to the rest of the word.
  4. Join the words array into a string with .join(' ').

For example:

index.js

function capitalizeWords(str) {
  return str
    .toLowerCase()
    .split(' ')
    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
    .join(' ');
}


// Welcome To Coding Beauty
console.log(capitalizeWords('WELCOME to coding beauty'));

// JavaScript And TypeScript
console.log(capitalizeWords('JAVASCRIPT AND TYPESCRIPT'));

Our capitalizeWords() function takes a string and returns a new string with all the words capitalized.

First, we use the toLowerCase() method to lowercase the entire string, ensuring that only the first letter of each word is uppercase.

// welcome to coding beauty
console.log('WELCOME to coding beauty'.toLowerCase());

Tip: If it’s not necessary for the remaining letters in each word to be lowercase, you can remove the call to the toLowerCase() method.

Then we call the String split() method on the string to split all the words into an array.

// [ 'welcome', 'to', 'coding', 'beauty' ]
console.log('welcome to coding beauty'.split(' '));

After creating the array, we call the map() method on it, with a callback function as an argument. This function will be called and return a result for each word in the array.

In the function, we get the word’s first character with charAt(), convert it to uppercase with toUpperCase(), and concatenate it with the rest of the string.

We use the String slice() method to get the remaining part of the string. Passing 1 to slice() makes it return the portion of the string from the second (index of 1) character to the end.

// [ 'Welcome', 'To', 'Coding', 'Beauty' ]
console.log(
  'welcome to coding beauty'
    .split(' ')
    .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
);

So map() returns an array containing all the words in the string, with each word’s first letter capitalized.

Lastly, we concatenate all the words in a single string, with the Array join() method.

Passing a space (' ') to join() separates the words by a space in the resulting string.

// Welcome To Coding Beauty
console.log(['Welcome', 'To', 'Coding', 'Beauty'].join(' '));

How to fix the “__dirname is not defined in ES module scope” error in JavaScript

The “__dirname is not defined in ES module scope” error happens in JavaScript when we try to access the __dirname global variable in an ES module. The __dirname and __filename global variables are defined in CommonJS modules, but not in ES modules.

The "__dirname is not defined in ES module scope" error occurring in JavaScript.
The “__dirname is not defined in ES module scope” error occurring in JavaScript.

We can fix the “__dirname is not defined in ES module scope” error by using certain functions to create a custom __dirname variable that works just like the global variable, containing the full path of the file’s current working directly.

index.js

import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);

const __dirname = path.dirname(__filename);

// C:/cb/cb-js
console.log(__dirname);

// C:\cb\cb-js\index.html
console.log(path.join(__dirname, 'index.html'));

The import.meta object contains context-specific metadata associated with a certain module, e.g., a module’s file URL.

// file:///C:/cb/cb-js/index.js
console.log(import.meta.url);

So we get the current module’s file URL and pass it to the fileURLToPath function from the url module, to convert it to a file path. fileURLToPath returns a fully-resolved, platform-specific Node.js file path.

// C:\cb\cb-js\index.js
console.log(fileURLToPath('file:///C:/cb/cb-js/index.js'));

After getting the file path, we pass it to the dirname method from the path module, to get the full directory path from the file path.

// C:\cb\cb-js
console.log(path.dirname('C:\\cb\\cb-js\\index.js'));

With this, we now have our own __dirname and __filename variables.

Here’s the output from logging them from a file on my computer.

Logging the "__dirname" and "__filename" variables to the console.
Logging the __dirname and __filename variables to the console.

__filename contains the absolute path of the current module file.

__dirname contains the absolute path of the current module file’s directory.

Create utility for __dirname and __filename

If we access the __dirname and __filename variables frequently, we can abstract the logic for creating them in a utility module and avoid unnecessary repetition.

file-dir-name.js

import { fileURLToPath } from 'url';
import { dirname } from 'path';

export default function fileDirName(meta) {
  const __filename = fileURLToPath(meta.url);

  const __dirname = dirname(__filename);

  return { __dirname, __filename };
}

We’ll be able to use this utility across the various other module files in our project.

index.js

import fileDirName from './file-dir-name.js';

const { __dirname, __filename } = fileDirName(import.meta);

// C:\cb\cb-js
console.log(__dirname);

// C:\cb\cb-js\index.js
console.log(__filename);

How to Use an Image as a Link in React

Related: How to Link an Image in React

To use an image as a link in React, wrap the image in an anchor (a) tag. Clicking an image link will make the browser navigate to the specified URL.

For example:

App.js

import cbLogo from './cb-logo.png';

export default function App() {
  return (
    <div>
      Click the logo to navigate to the site
      <br />
      <br />
      <a href="https://wp.codingbeautydev.com" target="_blank" rel="noreferrer">
        <img src={cbLogo} alt="Coding Beauty logo"></img>
      </a>
    </div>
  );
}
The browser navigates to the URL when the image link is clicked.
The browser navigates to the URL when the image link is clicked.

We use an import statement to link the image into the file, and assign it to the src prop of the img element to display it.

The properties set on an a element will work as usual when it wraps an image. For instance, in the example, we set the a element’s target property to _blank, to open the URL in a new tab. Removing this will make it open in the same tab as normal.

We also set the rel prop to noreferrer for security purposes. It prevents the opened page from gaining access to any information about the page from which it was opened from.

For React Router, you can use an image as link by wrapping the image in a Link element.

For example:

ImagePages.jsx

import { Link } from 'react-router-dom';

export default function ImagesPage() {
  return (
    <div>
      <Link to="/nature" target="_blank" rel="noreferrer">
        <img src="/photos/tree-1.png" alt="Nature"></img>
      </Link>
    </div>
  );
}

How to Get an Input Field’s Value in React

To get the value of an input field in React:

  1. Create a state variable to store the input field’s value.
  2. Set an onChange event handler on the input field.
  3. In the event handler, assign event.target.value to the state variable.
  4. The state variable will contains the input field’s value at any given time.

For example:

App.js

import { useState } from 'react';

export default function App() {
  const [message, setMessage] = useState('');

  const [updated, setUpdated] = useState(message);

  const handleChange = (event) => {
    setMessage(event.target.value);
  };

  const handleClick = () => {
    //  "message" stores input field value
    setUpdated(message);
  };

  return (
    <div>
      <input
        type="text"
        id="message"
        name="message"
        onChange={handleChange}
        value={message}
      />

      <h2>Message: {message}</h2>

      <h2>Updated: {updated}</h2>

      <button onClick={handleClick}>Update</button>
    </div>
  );
}

With the useState hook, we create a state variable (message) to store the input field’s current value. We also create another state variable (updated) that will be updated with the input field value when the button is clicked.

We set an onChange event handler on the input field to execute an action. In the handler, we use the Event object’s target property to access the object representing the input element. The value property of this object contains the input value, so we pass it to setMessage to update message, and this reflects on the page.

After setting up the controlled input, we can now use message outside the handleChange handler to get the current value of the input field.

So in the onClick event handler we set on the button, we use setUpdated(message) to update the updated variable with the input field’s current value.

Get input value with ref

Alternatively, we can use a ref to get the value of an input field in React. After setting the ref on the input, we’ll be able to use the ref object to access the input field’s current value in the event handler.

App.js

import { useRef, useState } from 'react';

export default function App() {
  const inputRef = useRef(null);

  const [updated, setUpdated] = useState('');

  const handleKeyDown = (event) => {
    if (event.key === 'Enter') {
      setUpdated(inputRef.current.value);
    }
  };

  return (
    <div>
      <input
        ref={inputRef}
        type="text"
        id="message"
        name="message"
        onKeyDown={handleKeyDown}
      />

      <h2>Updated: {updated}</h2>
    </div>
  );
}

We set an onKeyDown event listener on the input to perform an action when a key is pressed. In this listener, we use the KeyboardEvent object’s key property to check if the key pressed is Enter, and if so, we use the ref object to get the input’s value and update the updated variable with it.

While the data in a controlled input is handled by React state, the data in an uncontrolled input is handled by the DOM itself. This is why the input in the example above doesnโ€™t have a value prop or onChange event handler set. Instead, we access the input field value with a React ref. The DOM updates this value when the text in the input is changed.

We create a ref object with the useRef hook and set it to the ref prop of the input. Doing this sets the current property of the ref object to the DOM object that represents the input element.

useRef returns a mutable ref object that does not change value when a component is updated. Also, modifying the value of this objectโ€™s current property does not cause a re-render. This is in contrast to the setState update function returned from useState.

Although the React documentation recommends using controlled components, uncontrolled components offer some advantages. You might prefer them if the form is simple and doesnโ€™t need instant validation, and values only need to be accessed on submission.

How to Check if a Checkbox is Checked in Vue.js

To check if a checkbox is checked in Vue:

  1. Create a boolean state variable to store the value of the checkbox.
  2. Use v-model to set up a two-way binding between the checkbox’s value and the state variable.
  3. If the checkbox is checked, the state variable will be true. Otherwise, it will be false.

For example:

App.vue

<template>
  <div id="app">
    <input
      type="checkbox"
      v-model="agreement"
      name="agreement"
    />

    <label for="agreement">
      I agree to the terms and conditions
    </label>

    <br /><br />

    <button :disabled="!agreement">Continue</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      agreement: false,
    };
  },
};
</script>
The button is disabled when the checkbox is checked, and enabled when it is unchecked.
The button is disabled when the checkbox is checked, and enabled when it is unchecked.

The checked property of the checkbox object indicates whether or not the checkbox is checked.

Every time the checkbox is checked or unchecked, the agreement state variable will be automatically updated to true or false respectively.

We set the button‘s disabled prop to the negation of agreement to disable and enable it when agreement is true and false respectively.

Check if checkbox is checked with ref

In most cases, v-model will be sufficient for checking if a checkbox if a checked in Vue. However, we can also use the ref attribute to get the input value. We can set this attribute on any DOM element and use the $refs property of the Vue instance to access the object that represents the element.

For example:

<template>
  <div id="app">
    <!-- ๐Ÿ‘‡ Set "ref" prop to create new ref -->
    <input
      type="checkbox"
      name="js"
      ref="theCheckbox"
    />

    <label for="js"> JavaScript </label>
    <br />
    <button @click="handleClick">Done</button>
    <p v-if="message">{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return { message: '' };
  },
  methods: {
    handleClick() {
      // ๐Ÿ‘‡ Access ref with "$refs" property
      if (this.$refs.theCheckbox.checked) {
        this.message = 'You know JS';
      } else {
        this.message = "You don't know JS";
      }
    },
  },
};
</script>
The checked state of the checkbox determines the message displayed when the button is clicked.
The checked state of the checkbox determines the message displayed when the button is clicked.

We set an onClick listener on the button. In this listener, we access the checkbox object using the ref, and use its checked property to determine the message that should be shown to the user when the button is clicked.

How to Push an Object to an Array in JavaScript

To push an object to an array in JavaScript, call the push() method on the array with the object as an argument, i.e., arr.push(obj). The push() method will add the element to the end of the array.

For example:

const arr = [];

const obj = { name: 'Jeff' };

// ๐Ÿ‘‡ Push object to array
arr.push(obj);

// [{ name: 'Jeff' } ]
console.log(arr);

The push() method takes an object and adds it to the end of an array.

Push object to array during initialization

If the variable is newly created just before the object is pushed (like in the previous example), you can simply place the object in between the square brackets ([]) to include it in the array as the variable is initialized:

const obj = { name: 'Jeff' };

// ๐Ÿ‘‡ Push object to array with initialization
const arr = [obj];

console.log(arr);

Push multiple objects to array

The push() method actually accepts a variable number of arguments. They are each added to the end of the array, in the order in which they are passed to push().

For example:

const arr = [];

const obj1 = { name: 'Samantha' };
const obj2 = { name: 'Chris' };
const obj3 = { name: 'Mike' };

arr.push(obj1, obj2, obj3);

// [ { name: 'Samantha' }, { name: 'Chris' }, { name: 'Mike' } ]
console.log(arr);

Push object to array without mutation

The push() method adds an object to the array in place, which means it gets modified. If you don’t want this, you can use the spread syntax (...) to create a copy of the original array, before calling push():

const arr = [{ name: 'Jerry' }];

const newArr = [...arr];

newArr.push({ name: 'Mia' });

// [ { name: 'Jerry' }, { name: 'Mia' } ]
console.log(newArr);

// ๐Ÿ‘‡ Original not modified
console.log(arr); // [ { name: 'Jerry' } ]

Similar to what we did earlier, we can include the object in the square brackets, after the spread syntax, to push the object to the array’s copy as it is initialized:

const arr = [{ name: 'Jerry' }];

// ๐Ÿ‘‡ Push object to array without mutation
const newArr = [...arr, { name: 'Mia' }];

// [ { name: 'Jerry' }, { name: 'Mia' } ]
console.log(newArr);

// Original not modified
console.log(arr); // [ { name: 'Jerry' } ]

While not always necessary, by avoiding mutations we can make our code more readable, predictable, and modular.

How to Remove a Class From Multiple Elements With JavaScript

To remove a class from multiple elements in JavaScript:

  1. Get a list of all the elements with a method like document.querySelectorAll(selector).
  2. Iterate over the list with forEach().
  3. For each element, call classList.remove(class) to remove the class from each element.

i.e.:

const elements = document.querySelectorAll('.class');

elements.forEach((element) => {
  element.classList.remove('class');
});

For example:

HTML

<p class="big bold text">Coding</p>
<p class="big bold text">Beauty</p>
<p class="big bold text">Dev</p>

<button id="remove">Remove class</button>

JavaScript

const removeBtn = document.getElementById('remove');

removeBtn.addEventListener('click', () => {
  const elements = document.querySelectorAll('.text');

  elements.forEach((element) => {
    element.classList.remove('big');
  });
});

CSS

.bold {
  font-weight: bold;
}

.big {
  font-size: 1.2em;
}

This will be the HTML after the button is clicked:

<p class="bold text">Coding</p>
<p class="bold text">Beauty</p>
<p class="bold text">Dev</p>

<button id="remove">Remove class</button>
The big class is removed from the texts when the button is clicked.
The big class is removed from the texts when the button is clicked.

We use the document.querySelectorAll() method to select all DOM elements from which we want to remove the class.

We iterate over the elements in the list object with the forEach() method. This forEach() method works similarly to Array forEach().

document.getElementsByClassName() method

We can use the document.getElementsByClassName() method in place of the document.querySelectorAll() method when the selector is a class selector. For getElementsByClassName(), we pass the class name without the . (dot), and we use Array.from() to convert the result to an array before the iteration with forEach().


const elements = Array.from(document.getElementsByClassName('text'));
elements.forEach((element) => {
  element.classList.remove('big');
});

classList.remove() method

We use the classList.remove() method to remove a class from the elements. You can remove multiple classes by passing more arguments to remove().

const elements = document.querySelectorAll('.text');

elements.forEach((element) => {
  element.classList.remove('big', 'bold');
});

If any of the classes passed to remove() doesn’t exist on the element, remove() will ignore it, instead of throwing an error.

Remove class from multiple elements with different selectors

Sometimes there is no common selector between the elements that you want to remove the class from. For such a case, you can pass multiple comma-separated selectors to the querySelectorAll() method.

const elements = document.querySelectorAll('.text, #box-1, #box-2');

elements.forEach((element) => {
  element.classList.remove('bold');
});

Add class to multiple elements

Just like the classList.remove() method removes one or more classes from an element, the classList.add() method adds one or more classes to an element. This means that we can use it in the forEach() method to remove a class from multiple DOM elements:

const elements = document.querySelectorAll('.text');
elements.forEach((element) => {
  element.classList.add('italic', 'underline');
});

How to Sort an Object Array by a Boolean Property in JavaScript

To sort an array of objects by a boolean property in JavaScript, call the sort() method on the array with a callback as an argument. In the function, use the boolean property to return an expression that evaluates to a positive number, a negative number, or zero. This will sort the array in place, by the boolean property, i.e.:

const trueFirst = users.sort((a, b) => b.boolProp - a.boolProp);

const falseFirst = users.sort((a, b) => a.boolProp - b.boolProp);

For example:

const users = [
  { name: 'Kate', premium: false },
  { name: 'Bob', premium: true },
  { name: 'Jeff', premium: false },
  { name: 'Samantha', premium: true },
];

const premiumFirst = users.sort((a, b) => b.premium - a.premium);

/*
  [
    { name: 'Bob', premium: true },
    { name: 'Samantha', premium: true },
    { name: 'Kate', premium: false },
    { name: 'Jeff', premium: false }
  ]
*/

console.log(premiumFirst);

const nonPremiumFirst = users.sort((a, b) => a.premium - b.premium);

/*
  [
    { name: 'Kate', premium: false },
    { name: 'Jeff', premium: false },
    { name: 'Bob', premium: true },
    { name: 'Samantha', premium: true }
  ]
*/

console.log(nonPremiumFirst);

The Array sort() method takes a callback function as an argument. It uses the value returned from this callback to determine the order in which to sort the values. For each item pair in the array, a and b:

If the returned value is negative, a is placed before b in the sorted array.

If the value is positive, b is placed before a.

If the value is 0, the order of a and b is not changed.

const arr = [5, 3, 8, 1, 7];

// Sort in ascending order
arr.sort((a, b) => a - b);

console.log(arr); // [ 1, 3, 5, 7, 8 ]

// Sort in descending order
const desc = arr.sort((a, b) => b - a);

console.log(desc); // [ 8, 7, 5, 3, 1 ]

Because we used the subtraction operator (-) on the boolean values, they are coerced to numbers before the subtraction happens. Truthy values are coerced to 1, and falsy values 0.

console.log(true + true); // 2

console.log(true + false); // 1

console.log(false + false); // 0

After seeing how the sort() method and boolean coercion works, it’s easy to understand the way the was sorted to place the objects with a boolean property value of true above the ones with false, and vice versa.

Sort object array by boolean property without mutation

The sort() method sorts the array in place, which means it gets modified. To prevent this, we can use the spread syntax (...) to create a shallow copy of the array for the sort:

const users = [
  { name: 'Kate', premium: false },
  { name: 'Bob', premium: true },
  { name: 'Jeff', premium: false },
  { name: 'Samantha', premium: true },
];

// ๐Ÿ‘‡ Clone array with spread syntax
const premiumFirst = [...users].sort((a, b) => b.premium - a.premium);

/*
  [
    { name: 'Bob', premium: true },
    { name: 'Samantha', premium: true },
    { name: 'Kate', premium: false },
    { name: 'Jeff', premium: false }
  ]
*/

console.log(premiumFirst);

/*
  Original not modified:
  [
    { name: 'Kate', premium: false },
    { name: 'Bob', premium: true },
    { name: 'Jeff', premium: false },
    { name: 'Samantha', premium: true }
  ]
*/

console.log(users);

By avoiding mutations, we can make our code more readable, predictable, and modular.

Easy Endless Infinite Scroll With JavaScript

In this article, we’re going to learn how to easily implement infinite scrolling on a webpage using JavaScript.

What is infinite scroll?

Infinite scroll is a web design technique where more content is loaded automatically when the user scrolls down to the end. It removes the need for pagination and can increase the time users spend on our site.

Finished infinite scroll project

Our case study for this article will be a small project that demonstrates essential concepts related to infinite scroll.

Here it is:

HTML structure

Before looking at the JavaScript functionality, let’s check out the HTML markup for the project’s webpage.

HTML

<div id="load-trigger-wrapper">
  <div id="image-container"></div>
  <div id="load-trigger"></div>
</div>
<div id="bottom-panel">
  Images:
  &nbsp;<b><span id="image-count"></span>
  &nbsp;</b>/
  &nbsp;<b><span id="image-total"></span></b>
</div>

The #image-container div will contain the grid of images.

The #load-trigger div is observed by an Intersection Observer; more images will be loaded when this div comes within a certain distance of the bottom of the viewport.

The #bottom-panel div will contain an indicator of the number of images that have been loaded.

Detect scroll to content end

The detectScroll() function uses the Intersection Observer API to detect when the #bottom-panel div comes within a certain range of the viewport’s bottom. We set a root margin of -30px, so this range is 30px upwards from the bottom.

JavaScript

const loadTrigger = document.getElementById('load-trigger');

// ...

const observer = detectScroll();

// ...

function detectScroll() {
  const observer = new IntersectionObserver(
    (entries) => {
      for (const entry of entries) {
        // ...
            loadMoreImages();
        // ...          
      }
    },
    // Set "rootMargin" because of #bottom-panel height
    { rootMargin: '-30px' }
  );

  // Start watching #load-trigger div
  observer.observe(loadTrigger);

  return observer;
}

The callback passed to and Intersection Observer fires after the observe() call, so the images are loaded as the page is loaded.

Display skeleton images

Before the actual images are loaded, we first show a blank skeleton image with a loading animation. We store the image elements in an array variable to update them when their respective images have been loaded.

JavaScript

const imageClass = 'image';
const skeletonImageClass = 'skeleton-image';

// ...

// This function would make requests to an image server
function loadMoreImages() {
  const newImageElements = [];
  // ...

  for (let i = 0; i < amountToLoad; i++) {
    const image = document.createElement('div');

    // Indicate image load
    image.classList.add(imageClass, skeletonImageClass);

    // Include image in container
    imageContainer.appendChild(image);

    // Store in temp array to update with actual image when loaded
    newImageElements.push(image);
  }

  // ...
}

To display each image, we create a div and add the image and skeleton-image classes to it. Here are the CSS definitions for these classes:

CSS

.image,
.skeleton-image {
  height: 50vh;
  border-radius: 5px;
  border: 1px solid #c0c0c0;
  /* Three per row, with space for margin */
  width: calc((100% / 3) - 24px);

  /* Initial color before loading animation */
  background-color: #eaeaea;

  /* Grid spacing */
  margin: 8px;

  /* Fit into grid */
  display: inline-block;
}

.skeleton-image {
  transition: all 200ms ease-in;

  /* Contain ::after element with absolute positioning */
  position: relative;

  /* Prevent overflow from ::after element */
  overflow: hidden;
}

.skeleton-image::after {
  content: "";

  /* Cover .skeleton-image div*/
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;

  /* Setup for slide-in animation */
  transform: translateX(-100%);

  /* Loader image */
  background-image: linear-gradient(90deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.2) 20%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0));

  /* Continue animation until image load*/
  animation: load 1s infinite;
}

@keyframes load {
  /* Slide-in animation */
  100% {
    transform: translateX(100%)
  }
}
The skeleton loading animation.
The skeleton loading animation.

Update skeleton images

Instead of getting images from a server, we get colors. After all the colors are loaded, we loop through the skeleton images, and for each image, we remove the skeleton-image class and apply the color.

JavaScript

function loadMoreImages() {
  // ...
  // Create skeleton images and stored them in "newImageElements" variable

  // Simulate delay from network request
  setTimeout(() => {
    // Colors instead of images
    const colors = getColors(amountToLoad);
    for (let i = 0; i < colors.length; i++) {
      const color = colors[i];
      newImageElements[i].classList.remove(skeletonImageClass);
      newImageElements[i].style.backgroundColor = color;
    }
  }, 2000);

  // ...
}

The getRandomColor() function takes a number and returns an array with that number of random colors.

JavaScript

function getColors(count) {
  const result = [];
  let randUrl = undefined;

  while (result.length < count) {
    // Prevent duplicate images
    while (!randUrl || result.includes(randUrl)) {
      randUrl = getRandomColor();
    }

    result.push(randUrl);
  }

  return result;
}

getColors() uses a getRandomColor() function that returns a random color, as its name says.

JavaScript

function getRandomColor() {
  const h = Math.floor(Math.random() * 360);

  return `hsl(${h}deg, 90%, 85%)`;
}

Stop infinite scroll

To save resources, we stop observing the load trigger element after all possible content has been loaded.

Let’s say we have 50 images that can be loaded and the loadLimit is 9. When the first batch of images is loaded, the amountToLoad should be 9, with 9 displayed images. When the fifth batch is to be loaded, the amountToLoad should still be 9, with 45 displayed images.

On the sixth batch, there’ll only be 5 images left to load, so the amountToLoad should now be 5, taking the displayed images to 50. This sixth batch of images will be the final one to be loaded, after which we’ll stop watching the load trigger element, with a call to the unobserve() method of the Intersection Observer.

So we use the Math.min() method to ensure that the amountToLoad is always correct. amountToLoad should never be more than the, and never less than the images left to load.

JavaScript

const imageCountText = document.getElementById('image-count');

// ...

let imagesShown = 0;

// ...

function loadMoreImages() {
  // ...
  const amountToLoad = Math.min(loadLimit, imageLimit - imagesShown);
  

  // Load skeleton images...
  // Update skeleton images...

  // Update image count
  imagesShown += amountToLoad;
  imageCountText.innerText = imagesShown;

  if (imagesShown === imageLimit) {
    observer.unobserve(loadTrigger);
  }
}

Optimize performance with throttling

If the user scrolls down rapidly, it’s likely that the Intersection Observer will fire multiple times, causing multiple image loads in a short period of time, creating performance problems.

To prevent this, we can use a timer to limit the number of times multiple image batches can be loaded within a certain time period. This is called throttling.

The throttle() function accepts a callback and a time period in milliseconds (time). It will not invoke the callback if the current throttle() call was made withing time ms of the last throttle() call.

JavaScript

let throttleTimer;

// Only one image batch can be loaded within a second
const throttleTime = 1000;

// ...

function throttle(callback, time) {
  // Prevent additional calls until timeout elapses
  if (throttleTimer) {
    console.log('throttling');
    return;
  }
  throttleTimer = true;

  setTimeout(() => {
    callback();

    // Allow additional calls after timeout elapses
    throttleTimer = false;
  }, time);
}

By calling throttle() in the Intersection Observer’s callback with a time of 1000, we ensure that loadMoreImages() is never called multiple times within a second.


function detectScroll() {
  const observer = new IntersectionObserver(
    (entries) => {
          // ...
          throttle(() => {
            loadMoreImages();
          }, throttleTime);
        }
      }
    },
    // ...
  );

  // ...
}

Finished infinite scroll project

You can check out the complete source code for this project in CodePen. Here’s an embed:

Conclusion

In the article, we learned the basic elements need to implement infinite scroll functionality using JavaScript. With the Intersection Observer API, we observe a load trigger element and load more content when the element gets within a certain distance of the viewport’s bottom. With these ideas in mind, you should able to easily add infinite scroll to your project, customized according to your unique needs.

How to Convert XML to JSON in Node.js

We can use the xml-js library from NPM to easily convert XML to JSON in Node.js, i.e.:

import { xml2json } from 'xml-js';

// ...

const json = xml2json(xml);

For example:

index.js

import { xml2json } from 'xml-js';
import fs from 'fs/promises';

// Read from file
// const xml = await fs.readFile('data.xml',  { encoding: 'utf-8' });

const xml = `<name>Garage</name>
<cars>
    <color>red</color>
    <maxSpeed>120</maxSpeed>
    <age>2</age>
</cars>
<cars>
    <color>blue</color>
    <maxSpeed>100</maxSpeed>
    <age>3</age>
</cars>
<cars>
    <color>green</color>
    <maxSpeed>130</maxSpeed>
    <age>2</age>
</cars>`;

const json = xmltojson(xml, );

console.log(json);

This code will have the following JSON output:

{"declaration":{"attributes":{"version":"1.0","encoding":"UTF-8"}},"elements":[{"type":"element","name":"languages","elements":[{"type":"element","name":"language","elements":[{"type":"text","text":"\n      English\n   "}]},{"type":"element","name":"language","elements":[{"type":"text","text":"\n      French\n   "}]},{"type":"element","name":"language","elements":[{"type":"text","text":"\n      Spanish\n   "}]}]}]}

What are XML and JSON?

Let’s go through these terms in case you’re not familiar with them

XML (eXtensible Markup Language) is a markup language similar to HTML that was designed to store and transport data. Unlike HTML, XML doesn’t have any predefined tags. Instead, we define our own tags according to our requirements.

Example of XML data:

<users>
   <user>
     <name>Jack</name>
   </user>
   <user>
     <name>Samantha</name>
   </user>
</users>

JSON (JavaScript Object Notation) is a text format used to store and transport data based on JavaScript object syntax and is commonly used to build RESTful APIs.

Example of JSON data:

{
  "users": [
    {
      "name": "Jack"
    },
    {
      "name": "Samantha"
    }
  ]
}

You saw more examples of XML and JSON data in the previous section.

Install xml-js

Before using xml-js, we need to install it in our Node.js project. We can do this with the NPM CLI.

npm i xml-js

# Yarn
yarn add xml-js

After installation, weโ€™ll be able to import it into a JavaScript module, like this:

import { xml2json } from 'xml-js';

// CommonJS
const { xml2json } = require('xml-js');

We use import destructuring to access the xml2json() method directly from the library.

The xml2json() function

The xml2json() function has two parameters. The first is the XML string to convert to JSON, and the second is an optional object.

const json = xml2json(xml, { spaces: 2, compact: true });

Customize conversion of XML to JSON in Node.js

We use this object to specify various options for customizing the conversion process.

In our example, we don’t pass an object, so the default options are used. We can pass an object with a spaces option to properly format and indent the resulting JSON.

import { xml2json } from 'xml-js';

const xml = `<?xml version="1.0" encoding="UTF-8"?>
<languages>
   <language>
      English
   </language>
   <language>
      French
   </language>
   <language>
      Spanish
   </language>
</languages>`;

// ๐Ÿ‘‡ Set "spaces" option
const json = xml2json(xml, { spaces: 2 });

console.log(json);

Here’s the new output:

{
  "declaration": {
    "attributes": {
      "version": "1.0",
      "encoding": "UTF-8"
    }
  },
  "elements": [
    {
      "type": "element",
      "name": "languages",
      "elements": [
        {
          "type": "element",
          "name": "language",
          "elements": [
            {
              "type": "text",
              "text": "\n      English\n   "
            }
          ]
        },
        {
          "type": "element",
          "name": "language",
          "elements": [
            {
              "type": "text",
              "text": "\n      French\n   "
            }
          ]
        },
        {
          "type": "element",
          "name": "language",
          "elements": [
            {
              "type": "text",
              "text": "\n      Spanish\n   "
            }
          ]
        }
      ]
    }
  ]
}

The compact property to determine whether the resulting JSON should be detailed or compact. It is false by default.

import { xml2json } from 'xml-js';

const xml = `<?xml version="1.0" encoding="UTF-8"?>
<languages>
   <language>
      English
   </language>
   <language>
      French
   </language>
   <language>
      Spanish
   </language>
</languages>`;

// ๐Ÿ‘‡ Set "compact" option
const json = xml2json(xml, { spaces: 2, compact: true });

console.log(json);

Now the resulting JSON will have a significantly smaller size than before:

{
  "_declaration": {
    "_attributes": {
      "version": "1.0",
      "encoding": "UTF-8"
    }
  },
  "languages": {
    "language": [
      {
        "_text": "\n      English\n   "
      },
      {
        "_text": "\n      French\n   "
      },
      {
        "_text": "\n      Spanish\n   "
      }
    ]
  }
}