DEV Community

Viktor
Viktor

Posted on

Webdev WTF

Have you ever hit a strange issue while doing frontend development and said WTF?

I thought it might be interesting to share these WTF moments and maybe learn something from each other and have a little laugh.

Here are some of the WTFs I've encountered in my 10+ years of frontend development.

document.querySelectorAll does not return an actual array

document.querySelectorAll does not return an actual array, but a NodeList instance that only partially acts like an array: typeof document.querySelectorAll('a').map === 'undefined'

NodeList has #forEach method, but doesn't have #map method. It's a mess. :)

Getting CSS styles of a DOM element in JS

There's el.style, but that only works for inline styles in HTML: <div style="...">. Then there's the global getComputedStyle(Element, pseudo) which almost works, but not quite (depending on what you need):

body > .container {
  background: none;
}
Enter fullscreen mode Exit fullscreen mode
getComputedStyle(containerEl).background
// => "rgba(0, 0, 0, 0) none repeat scroll 0% 0% / auto padding-box border-box"
Enter fullscreen mode Exit fullscreen mode

This happens because background is a shorthand property for many other properties (such as backgroundColor, etc.)
What if I want to get the exact CSS style from the CSS? I personally have no idea how to do that.

Super complicated to get URL params

window.location is not a string. It's not a URL either. It's...Location. Let's see how we could get the URL params.
An instance of URL class has: searchParams method. We can create a URL instance from Location:

// imagine window.location is https://example.com/events/new?premium=true#buy
const url = new URL(window.location)
const whyIsThisCalledSearchParams = url.searchParams
whyIsThisCalledSearchParams.toString() // => "premium=true"
Enter fullscreen mode Exit fullscreen mode

IT WORKS!!!! ๐Ÿ˜

Not so fast, Mr. Gordon.

JSON.stringify(whyIsThisCalledSearchParams) // => "{}"
// WAT
Enter fullscreen mode Exit fullscreen mode

Ok, well, there's #entries() method. I guess that's like Object.entries(), right? Let's use that:

Object.entries({ name: 'Viktor' })
// => [["name", "Viktor"]]
whyIsThisCalledSearchParams.entries()
// => Iterator {}
// WAT it's empty
Object.entries(new URL(location).searchParams)
// => []
// WAT it's empty
Enter fullscreen mode Exit fullscreen mode
whyIsThisCalledSearchParams.get('premium')
// => "true"
// of course, true is a string here, but that's ok
Enter fullscreen mode Exit fullscreen mode

The only way to get the url query parameters:

for (const [key, value] of whyIsThisCalledSearchParams) {}
Enter fullscreen mode Exit fullscreen mode

Or you can do the old-fashioned way:

// because first character is "?" because why not :)
const query = window.location.search.substring(1)
query.split('&').....each(queryPair => queryPair.split('='))
Enter fullscreen mode Exit fullscreen mode

And then you hope you won't need arrays in there...I also forgot to mention parsing any special characters...sorry! :)

Also, searchParams is called query in the official RFCs. To me, "query" or "query params" make more sense than "search params".

In JavaScript you can't get all form field values through standard API

formEl.formValues or similar doesn't exist. You have to do: formEl.querySelectorAll('input, select').map(el => el.value). But that won't work for several reasons. First, you can't use .map, but we'll get to that in a moment.
Second, you can have input fields outside of the formEl:

<input type="text" form="editform" name="id">

<form action="datamanager" id="editform"></form>
Enter fullscreen mode Exit fullscreen mode

This works.

So you need to do all this extra mumbo jumbo similar to this:

formEl.querySelectorAll('input, select').forEach(el => {
  if (el.type === 'checkbox' || el.type === 'radio') {
    el.checked
  } else {
    el.value
  }
}
if (formEl.id) {
  document.querySelectorAll('input[form="' + formEl.id + '"]').forEach(...)
}
Enter fullscreen mode Exit fullscreen mode

I lied. You can get the form values using the FormData class and its #entries() method. Maybe.
It might work, but it might fail silently (which is the worst possible way to fail, when you don't know it failed) if you decide to add an input element outside of the form tag programmatically:

const inputEl = document.createElement('input')
inputEl.name = 'firstName'
inputEl.form = 'customerForm'
inputEl.form
// => null
// WAT it just fails silently
inputEl.setAttribute('form', 'customerForm')
// => works as expected
Enter fullscreen mode Exit fullscreen mode

dataset vs data-* vs dataset.kebabCase vs data-kebab-case

In HTML you write: data-target-id="someid", while in JavaScript, to get that same attribute value, you say: el.dataset.targetId. WAT.

CSS usually (on a good day) prefers class names with kebab case, but JS prefers pascalCase:

.container {
  background-color: #ffffff;
`
Enter fullscreen mode Exit fullscreen mode
containerEl.style.backgroundColor = '#ffffff'
Enter fullscreen mode Exit fullscreen mode

Also check these great examples:

div {
  display: inline-block; /* kebab case, as the rest of the CSS */
  white-space: nowrap; /* nowrap? WAT no-wrap? WAT */
}
Enter fullscreen mode Exit fullscreen mode

How about this:

button {
  white-space: nowrap;
}
div {
  white-space: no-pre; /* What the... ๐Ÿคฏ */
}
Enter fullscreen mode Exit fullscreen mode

Getting element's X/Y position on the page from page top

This works:

el.getBoundingClientRect().top + window.pageYOffset
Enter fullscreen mode Exit fullscreen mode

Or does it?

Well, sort-of. It works if the element is not in a inside some container that's position fixed. So you first have to find check all of the element's ancestors to see if any one of those is position fixed and only then you can calculate this correctly. Please correct me if I'm wrong here. I'd love to be wrong here.

Does this look familiar: z-index: 9999999999999999?

You add z-index: 9999999999999999 to an element, but it's still not above all the other elements. Y U NO?
It's because z-index is "stacking". So it works kind of like this:

<div id="header" style="z-index: 2"></div>
<div id="main-content" style="z-index: 1">
  <div id="dis-one" style="z-index: 999"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

The #dis-one element has an overall z-index of 1.999, while #header has an overall z-index of 2. It stacks.

Well, yeah...maybe. But not always.

First of all, you need to add at least position: relative for z-index to have any effect. Secondly, new stacking contexts are also created when you apply a transform. So, sometimes to show .container:before behind .container you need to actually add another HTML element (let's call it) .wrapper around .container.

Check out this answer:
https://stackoverflow.com/a/20852489/336806

<input type="submit"> and <button>

There's a special input type called "submit", which kind-of renders like a button. It also has a value attribute, but it's usually not submitted to the backend. Remember our code for getting form values? Well, it does submit this value. That's another edge case you need to take care of.
It's also far more limiting than an actual <button>

Speaking of <button>:

The <button>

Here's a part of a website's HTML:

<button>Submit</button>
Enter fullscreen mode Exit fullscreen mode

...but it's not working. ๐Ÿค” The form doesn't get submitted when you click on this button.

You know that button's default type is type="submit". And you see no JavaScript code that could prevent the submission.
Then you realize, you put it outside of the <form>:

<form>
  <!-- form fields -->
</form>
<button>Submit</button>
Enter fullscreen mode Exit fullscreen mode

Well, if the <button> tag is placed outside of <form> tag, it behaves pretty much like <button type="button">.
It's the same HTML syntax that acts differently whether it's inside of <form> tag or not.

Similary, this is also a disaster in the making:

<form action="/events">
  <form action="/events/5">
    <button>Submit</button>
  </form>

  <button>Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Similarly:

<label>
  <input type="checkbox" name="terms">
  I accept the <a href="/terms">Terms & Conditions</a>
</label>
Enter fullscreen mode Exit fullscreen mode

Or (these examples are not for faint hearted):

<a href="/events/5">
  <div>Best Ice-cream in town</div>
  <a href="/events/5/more-info">
    More info about Ice-cream party
  </a>

  <button>Submit</button>
</a>
Enter fullscreen mode Exit fullscreen mode

Let's talk some more about JavaScript

typeof [] === 'object' // WAT
typeof null === 'object' // WAT
typeof undefined === 'undefined' // WAT
Enter fullscreen mode Exit fullscreen mode
const add = (a = 1, b = 2) => {
  if (a && b) {
    return a + b
  } else {
    return 0
  }
}
add(undefined, undefined) === 3 // hmmm...ok
add(2, null) === 0 // WAT
add(3, undefined) === 5 // WAT
Enter fullscreen mode Exit fullscreen mode

I'll wrap up here. Please do share your experiences working on frontend. :) Thanks!

Top comments (9)

Collapse
 
dz4k profile image
Deniz AkลŸimลŸek
Object.setPrototypeOf(NodeList.prototype, Array.prototype)
Enter fullscreen mode Exit fullscreen mode

Thank me later :)

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Idiomatic JavaScript, they say don't modify objects that you don't own, so I feel this is a bit of a sledge hammer really. Besides Array.from(someNodeList) does just fine.

Collapse
 
vfonic profile image
Viktor

What the...? :D I'm only guessing what that does, but if we weren't talking about this, I don't think I'd ever guess.

...Thank you! :)

Collapse
 
ashleyjsheridan profile image
Ashley Sheridan

For the form elements one, can you not use formElement.elements? That's how it used to work back in the IE 5 days, and to the best of my knowledge, it worked back when Netscape Navigator was still a thing.

As for some of the WTF's that I've encountered:

  • 100vh doesn't work as expected only in Safari on iOS because the browsers footer/action bar is included as par of the available height of vh, meaning the bar covers content
  • File upload button styling. Despite the element being around longer than Chrome has existed, it's still impossible to actually style if properly.
  • Font widths on Safari - text is all displayed just a little bit wider than on any other browser, leading to breaks in text that weren't expected
  • Automatic placement of elements within a CSS grid in IE11. Child elements need to be explicitely placed within the grid, making it a whole lot less useful when dealing with dynamic unknown numbers of children.
  • Trailing commas in array items, I'm just so used to them in other languages. Support for them came so late for Javascript, but because some older browser still don't support them, they're sometimes still just not usable. Oh, and we still can't use them in JSON.
  • String padding functionality. Again, as IE11 doesn't support it, but is still often on our support matrix, we end up using polyfills, or worse (remember the leftpad problem that broke builds everywhere?!)
  • Unicode regular expressions. Being able to use selectors like \p{L} to select any letter (including those from non-ascii, such as Russian Cyrillic or Chinese). Mileage varies greatly between what selectors are supported, and polyfills here are basically a must. Unicode support in JS has always felt a little like a bit of an afterthough in places.
  • Automatic semicolon insertion. This leads to some weird bugs that you can sink hours into figuring out.
  • Math.max() (with no arguments) returns -Infinity and Math.min() returns Infinity, so Math.min() > Math.max(), rather than doing something sane like return NaN or erroring out when there's no arguments.
Collapse
 
extory3 profile image
Madiyar Ibrayev • Edited

Hmm, I remember how I wasted 2 hours on trying to copy children nodes from one element to another querySelectorAll's children with forEach and for iteration, both didn't work either. The problem was that I initialized variable and assigned selectorAll to it, tried to manipulate children which resulted in error logs in console. However, I came up with another solution: directly typing "querySelectorAll(ParentElement).childNodes" - which didn't cause any bugs. But still, WTF?

Collapse
 
metalmikester profile image
Michel Renaud

I'm currently doing web development and it's a big WTF, period. That makes it both interesting and maddening at the same time. I run into weird "magic" things on a daily basis. It's often hard to figure out what's going on.. "oooh magic! NOOOOOO!!!!!"

Collapse
 
alohci profile image
Nicholas Stimpson

A definite WTF the first time you come across it, is DOM's live collection, because it's so rarely seen elsewhere. This idea that if you get the collection of elements from, say, getElementsByClassName(), and loop over an index of the elements in a forward direction, removing each one from the DOM as you go, because the collection updates itself after each removal, you end up only removing every other element.

Collapse
 
adam_cyclones profile image
Adam Crockett ๐ŸŒ€

Behold!

div {
    background: background;
}
Enter fullscreen mode Exit fullscreen mode

It's a different color depending on your device.

Collapse
 
marklai1998 profile image
Mark Lai

URLSearchParams Can definitely helps the query string problem

developer.mozilla.org/en-US/docs/W...