dev

Helpful Utility Functions: `getElement`


I am a strong believer in building smaller functions that do one thing well, can be reused in multiple places, and can be used to build more complex functions. If this idea sounds familiar, well it should, because it’s the core principle of atomic design, and functional programming. It’s not a groundbreaking philosophy, but is incredibly useful in day-to-day work.

The primary project I work on is for a large company, with 14(ish) websites that do custom experiences if users meet certain criteria. My code is injected after the the page has rendered for the client and I do not have direct access to the codebase, but I need to manipulate the DOM. Most of the sites are served statically from a CMS but at least two are React applications, which create different challenges. I need to ensure my utility functions work after a user loads the page and DOM has been rendered, or when an React component re-renders.

getElement

function getElement(selector) {
  const element = document.querySelector(selector);

  // Log an error if the element is not found and return null
  if (!element) {
    console.error(`❌ QA getElement Error: can't find ${selector}`);
    return null;
  }

  return element;
}

getElement checks for an element in the DOM and logs an error if it can’t be found. It’s straightforward, but is incredibly valuable for me. This functions checks if an element is the DOM, or lets me know if it’s not. This alone makes QA much easier if something goes wrong. The website can change without me know it, and know what’s missing is difficult but this functions sends up a flag if something is missing. A simple querySelector doesn’t work because it fails silently and I need to know if (and which) elements are missing. This alone is helpful, but I need more safety checks to ensure my code runs correctly.

function getElement(selectorOrElement) {
  // Declare element variable
  let element;

  // Check if selectorOrElement is a string or an HTMLElement
  if (typeof selectorOrElement === "string") {
    // Attempt to find the element in the DOM
    element = document.querySelector(selectorOrElement);

    // Throw an error if the element is not found
    if (!element) {
      console.error(`❌ QA getElement Error: can't find ${selectorOrElement}`);
      return null;
    }
  } else if (selectorOrElement instanceof HTMLElement) {
    element = selectorOrElement;
  } else {
    // Handle case where selectorOrElement is neither a string nor an HTMLElement
    console.error(
      "❌ QA getElement Error: Invalid input. Input must be a string selector or an HTMLElement",
    );
    return null;
  }

  return element;
}

Above I’ve added type checking to see if the value passed to getElement is a string or an HTMLElement. In some instances, I pass elements directly to the function, because I have to inject elements into the DOM. It certainly is possible to convert the element value to a string, this just adds more flexibility to the function and saves me from converting the element to a string every time I need to use it. All this alone is great, but we can add a more modern way of logging errors with try/catch, and add a few more safety checks to ensure we’re getting exactly what we expect.

function getElement(selectorOrElement) {
  try {
    // Declare element variable
    let element;

    // Check if selectorOrElement is a string or an HTMLElement
    if (typeof selectorOrElement === "string") {
      // Attempt to find the element(s) in the DOM
      const foundElements = document.querySelectorAll(selectorOrElement);

      // Check if the selector is not found in the DOM
      if (foundElements.length === 0) {
        throw new Error(`can't find ` + selectorOrElement);
      }

      // Check if the selector is not unique
      if (foundElements.length > 1) {
        throw new Error(`more than one element found ` + selectorOrElement);
      }

      // Assign the single found element to 'element'
      element = foundElements[0];
    } else if (selectorOrElement instanceof HTMLElement) {
      element = selectorOrElement;
    } else {
      // Handle case where selectorOrElement is neither a string nor an HTMLElement
      throw new Error(
        "Invalid input: Input must be a string selector or an HTMLElement",
      );
    }

    return element;
  } catch (error) {
    // Log any errors that occur
    console.log("❌ QA getElement Error:", error.message);
    return null; // Return null if there was an error
  }
}

Above is the final form of my getElement function. I use a try/catch block to improve error handling in a more modern capacity. I’v also added a check to see if the selector is unique.

// Check if the selector is not unique
if (foundElements.length > 1) {
  throw new Error(`more than one element found ` + selectorOrElement);
}

Again, because the websites can change without me knowing, I need to ensure that I am targeting to correct DOM element. This is a flag letting me know that I very likely need to be more specify with my selector. This function is the foundation in many other helper functions like replaceElement, insertElement, and removeElement, which I can write about later, but they are all built upon getElement. My advanced version does most of the QA checking so the other functions don’t have too.

To me, the key to a good helper function is that it should be as agnostic as possible. getElement can be put on my project I currently work on and it will do what it’s supposed to: ensure I’m targeting the correct element in the DOM.

Usage

getElement(".card");